From: Mario Date: Sun, 2 Aug 2020 12:22:45 +0000 (+1000) Subject: Remove the g_ prefix from some server code files and rename sv_main to main X-Git-Tag: xonotic-v0.8.5~794 X-Git-Url: http://de.git.xonotic.org/?a=commitdiff_plain;h=77a72b8a1d5686ac0ee30d5019f512086bcbaf3e;p=xonotic%2Fxonotic-data.pk3dir.git Remove the g_ prefix from some server code files and rename sv_main to main --- diff --git a/qcsrc/common/gamemodes/gamemode/assault/sv_assault.qc b/qcsrc/common/gamemodes/gamemode/assault/sv_assault.qc index 3915429d6..fcfcf32ff 100644 --- a/qcsrc/common/gamemodes/gamemode/assault/sv_assault.qc +++ b/qcsrc/common/gamemodes/gamemode/assault/sv_assault.qc @@ -4,8 +4,8 @@ #include #include #include -#include -#include +#include +#include #include .entity sprite; diff --git a/qcsrc/common/gamemodes/gamemode/ctf/sv_ctf.qc b/qcsrc/common/gamemodes/gamemode/ctf/sv_ctf.qc index ec296a9c9..7e4366659 100644 --- a/qcsrc/common/gamemodes/gamemode/ctf/sv_ctf.qc +++ b/qcsrc/common/gamemodes/gamemode/ctf/sv_ctf.qc @@ -7,8 +7,8 @@ #include #include #include -#include -#include +#include +#include #include #include #include @@ -2205,7 +2205,7 @@ MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink) WaypointSprite_UpdateHealth(player.wps_flagcarrier, healtharmor_maxdamage(GetResource(player, RES_HEALTH), GetResource(player, RES_ARMOR), autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id).x); } -MUTATOR_HOOKFUNCTION(ctf, Damage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc +MUTATOR_HOOKFUNCTION(ctf, Damage_Calculate) // for changing damage and force values that are applied to players in damage.qc { entity frag_attacker = M_ARGV(1, entity); entity frag_target = M_ARGV(2, entity); @@ -2358,7 +2358,7 @@ MUTATOR_HOOKFUNCTION(ctf, PlayerUseKey) if(head != player && SAME_TEAM(head, player)) if(!head.speedrunning && !head.vehicle) { - // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc) + // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in damage.qc) vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head)); vector passer_center = CENTER_OR_VIEWOFS(player); diff --git a/qcsrc/common/gamemodes/gamemode/cts/sv_cts.qc b/qcsrc/common/gamemodes/gamemode/cts/sv_cts.qc index 22d021cf5..1550c8e89 100644 --- a/qcsrc/common/gamemodes/gamemode/cts/sv_cts.qc +++ b/qcsrc/common/gamemodes/gamemode/cts/sv_cts.qc @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include #include diff --git a/qcsrc/common/gamemodes/gamemode/domination/sv_domination.qc b/qcsrc/common/gamemodes/gamemode/domination/sv_domination.qc index 410d07e8f..0a6ee791a 100644 --- a/qcsrc/common/gamemodes/gamemode/domination/sv_domination.qc +++ b/qcsrc/common/gamemodes/gamemode/domination/sv_domination.qc @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include #include diff --git a/qcsrc/common/gamemodes/gamemode/invasion/sv_invasion.qc b/qcsrc/common/gamemodes/gamemode/invasion/sv_invasion.qc index 09be394fb..d74c2dfc2 100644 --- a/qcsrc/common/gamemodes/gamemode/invasion/sv_invasion.qc +++ b/qcsrc/common/gamemodes/gamemode/invasion/sv_invasion.qc @@ -6,7 +6,7 @@ #include #include -#include +#include #include IntrusiveList g_invasion_roundends; diff --git a/qcsrc/common/gamemodes/gamemode/keepaway/sv_keepaway.qc b/qcsrc/common/gamemodes/gamemode/keepaway/sv_keepaway.qc index 6ce13f2e9..dda6185cf 100644 --- a/qcsrc/common/gamemodes/gamemode/keepaway/sv_keepaway.qc +++ b/qcsrc/common/gamemodes/gamemode/keepaway/sv_keepaway.qc @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include .entity ballcarried; @@ -384,7 +384,7 @@ MUTATOR_HOOKFUNCTION(ka, PlayerUseKey) } } -MUTATOR_HOOKFUNCTION(ka, Damage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc +MUTATOR_HOOKFUNCTION(ka, Damage_Calculate) // for changing damage and force values that are applied to players in damage.qc { entity frag_attacker = M_ARGV(1, entity); entity frag_target = M_ARGV(2, entity); diff --git a/qcsrc/common/gamemodes/gamemode/keyhunt/sv_keyhunt.qc b/qcsrc/common/gamemodes/gamemode/keyhunt/sv_keyhunt.qc index 5e098c67c..d48c76f39 100644 --- a/qcsrc/common/gamemodes/gamemode/keyhunt/sv_keyhunt.qc +++ b/qcsrc/common/gamemodes/gamemode/keyhunt/sv_keyhunt.qc @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include diff --git a/qcsrc/common/gamemodes/gamemode/lms/sv_lms.qc b/qcsrc/common/gamemodes/gamemode/lms/sv_lms.qc index a206fcbde..1b7b527d8 100644 --- a/qcsrc/common/gamemodes/gamemode/lms/sv_lms.qc +++ b/qcsrc/common/gamemodes/gamemode/lms/sv_lms.qc @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include int autocvar_g_lms_extra_lives; diff --git a/qcsrc/common/gamemodes/gamemode/onslaught/sv_onslaught.qc b/qcsrc/common/gamemodes/gamemode/onslaught/sv_onslaught.qc index 6bb2387dd..375edafb4 100644 --- a/qcsrc/common/gamemodes/gamemode/onslaught/sv_onslaught.qc +++ b/qcsrc/common/gamemodes/gamemode/onslaught/sv_onslaught.qc @@ -4,7 +4,7 @@ #include #include -#include +#include #include #include #include diff --git a/qcsrc/common/gamemodes/gamemode/race/sv_race.qc b/qcsrc/common/gamemodes/gamemode/race/sv_race.qc index f56353d01..5c6e52f39 100644 --- a/qcsrc/common/gamemodes/gamemode/race/sv_race.qc +++ b/qcsrc/common/gamemodes/gamemode/race/sv_race.qc @@ -1,7 +1,7 @@ #include "sv_race.qh" #include -#include +#include #include #include #include diff --git a/qcsrc/common/mapobjects/func/breakable.qc b/qcsrc/common/mapobjects/func/breakable.qc index 24d713943..4db651658 100644 --- a/qcsrc/common/mapobjects/func/breakable.qc +++ b/qcsrc/common/mapobjects/func/breakable.qc @@ -1,7 +1,7 @@ #include "breakable.qh" #ifdef SVQC -#include +#include #include #include #include diff --git a/qcsrc/common/mapobjects/target/spawn.qc b/qcsrc/common/mapobjects/target/spawn.qc index 0c607934c..b96370d18 100644 --- a/qcsrc/common/mapobjects/target/spawn.qc +++ b/qcsrc/common/mapobjects/target/spawn.qc @@ -5,7 +5,7 @@ #include #include #include - #include + #include #endif #ifdef SVQC diff --git a/qcsrc/common/mapobjects/teleporters.qc b/qcsrc/common/mapobjects/teleporters.qc index 24b7fb99c..93bfcbb26 100644 --- a/qcsrc/common/mapobjects/teleporters.qc +++ b/qcsrc/common/mapobjects/teleporters.qc @@ -15,7 +15,7 @@ #include #include #include "../deathtypes/all.qh" - #include + #include #include "../turrets/sv_turrets.qh" #include "../vehicles/all.qh" #include diff --git a/qcsrc/common/mapobjects/trigger/jumppads.qc b/qcsrc/common/mapobjects/trigger/jumppads.qc index 66a9499d4..5273179ad 100644 --- a/qcsrc/common/mapobjects/trigger/jumppads.qc +++ b/qcsrc/common/mapobjects/trigger/jumppads.qc @@ -2,7 +2,7 @@ // TODO: split target_push and put it in the target folder #ifdef SVQC #include -#include +#include void trigger_push_use(entity this, entity actor, entity trigger) { diff --git a/qcsrc/common/monsters/monster.qh b/qcsrc/common/monsters/monster.qh index 146f64d40..5d79c4a83 100644 --- a/qcsrc/common/monsters/monster.qh +++ b/qcsrc/common/monsters/monster.qh @@ -59,7 +59,7 @@ ENDCLASS(Monster) #ifdef SVQC #include "sv_monsters.qh" -#include +#include #include #include #include diff --git a/qcsrc/common/monsters/sv_monsters.qc b/qcsrc/common/monsters/sv_monsters.qc index 46bb1593a..d5bff8f02 100644 --- a/qcsrc/common/monsters/sv_monsters.qc +++ b/qcsrc/common/monsters/sv_monsters.qc @@ -10,12 +10,12 @@ #include #include #include -#include +#include #include "../deathtypes/all.qh" #include #include #include -#include +#include #include "../turrets/sv_turrets.qh" #include "../turrets/util.qh" #include "../vehicles/all.qh" @@ -135,7 +135,7 @@ entity Monster_FindTarget(entity this) { if(Monster_ValidTarget(this, it)) { - // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc) + // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in damage.qc) vector targ_center = CENTER_OR_VIEWOFS(it); if(closest_target) diff --git a/qcsrc/common/mutators/mutator/breakablehook/sv_breakablehook.qc b/qcsrc/common/mutators/mutator/breakablehook/sv_breakablehook.qc index dc1bfefc2..c80b97d36 100644 --- a/qcsrc/common/mutators/mutator/breakablehook/sv_breakablehook.qc +++ b/qcsrc/common/mutators/mutator/breakablehook/sv_breakablehook.qc @@ -1,7 +1,7 @@ #include "sv_breakablehook.qh" #include -#include +#include REGISTER_MUTATOR(breakablehook, cvar("g_breakablehook")); diff --git a/qcsrc/common/mutators/mutator/buffs/sv_buffs.qc b/qcsrc/common/mutators/mutator/buffs/sv_buffs.qc index 29828a094..30cef339a 100644 --- a/qcsrc/common/mutators/mutator/buffs/sv_buffs.qc +++ b/qcsrc/common/mutators/mutator/buffs/sv_buffs.qc @@ -3,7 +3,7 @@ #include #include #include -#include +#include void buffs_DelayedInit(entity this); diff --git a/qcsrc/common/notifications/all.qc b/qcsrc/common/notifications/all.qc index c8fb372d6..c46c21722 100644 --- a/qcsrc/common/notifications/all.qc +++ b/qcsrc/common/notifications/all.qc @@ -8,7 +8,7 @@ #include #include #include - #include + #include #include #endif diff --git a/qcsrc/common/physics/player.qc b/qcsrc/common/physics/player.qc index af1ae0ad1..ade9c4d6f 100644 --- a/qcsrc/common/physics/player.qc +++ b/qcsrc/common/physics/player.qc @@ -8,7 +8,7 @@ #include #include #include "../mapobjects/trigger/viewloc.qh" -#include +#include // client side physics bool Physics_Valid(string thecvar) diff --git a/qcsrc/common/playerstats.qc b/qcsrc/common/playerstats.qc index d91e16f47..3b8c97c4b 100644 --- a/qcsrc/common/playerstats.qc +++ b/qcsrc/common/playerstats.qc @@ -9,7 +9,7 @@ #include "../server/anticheat.qh" #include #include "../server/scores.qh" - #include + #include #include "../server/weapons/accuracy.qh" #endif @@ -296,7 +296,7 @@ void PlayerStats_GameReport_Handler(entity fh, entity pass, float status) * G: game type * O: mod name (icon request) as in server browser * M: map name - * I: match ID (see "matchid" in g_world.qc) + * I: match ID (see "matchid" in world.qc) * S: "hostname" of the server * C: number of "unpure" cvar changes * U: UDP port number of the server diff --git a/qcsrc/common/turrets/sv_turrets.qc b/qcsrc/common/turrets/sv_turrets.qc index 84b0c8249..e2a9ab2e4 100644 --- a/qcsrc/common/turrets/sv_turrets.qc +++ b/qcsrc/common/turrets/sv_turrets.qc @@ -2,7 +2,7 @@ #ifdef SVQC #include #include -#include +#include #include #include #include diff --git a/qcsrc/common/vehicles/sv_vehicles.qc b/qcsrc/common/vehicles/sv_vehicles.qc index 590aba88a..beff57489 100644 --- a/qcsrc/common/vehicles/sv_vehicles.qc +++ b/qcsrc/common/vehicles/sv_vehicles.qc @@ -1,14 +1,14 @@ #include "sv_vehicles.qh" #include -#include -#include +#include +#include #include #include #include #include #include -#include +#include #include bool SendAuxiliaryXhair(entity this, entity to, int sf) diff --git a/qcsrc/common/weapons/all.qc b/qcsrc/common/weapons/all.qc index 62e4ff0e0..dc87d6b62 100644 --- a/qcsrc/common/weapons/all.qc +++ b/qcsrc/common/weapons/all.qc @@ -41,7 +41,7 @@ #include #include #include - #include + #include #endif #ifdef GAMEQC #include "calculations.qc" diff --git a/qcsrc/common/weapons/weapon/shockwave.qc b/qcsrc/common/weapons/weapon/shockwave.qc index f38c34b19..accb917f6 100644 --- a/qcsrc/common/weapons/weapon/shockwave.qc +++ b/qcsrc/common/weapons/weapon/shockwave.qc @@ -479,7 +479,7 @@ void W_Shockwave_Attack(Weapon thiswep, entity actor, .entity weaponentity) // BLAST CONE CALCULATION // ======================== - // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc) + // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in damage.qc) center = CENTER_OR_VIEWOFS(head); // find the closest point on the enemy to the center of the attack diff --git a/qcsrc/server/_mod.inc b/qcsrc/server/_mod.inc index 7634e7635..0da2e2be4 100644 --- a/qcsrc/server/_mod.inc +++ b/qcsrc/server/_mod.inc @@ -5,13 +5,14 @@ #include #include #include -#include -#include -#include +#include +#include +#include #include #include #include #include +#include #include #include #include @@ -24,9 +25,6 @@ #include #include #include -#ifdef SVQC - #include -#endif #include #include diff --git a/qcsrc/server/_mod.qh b/qcsrc/server/_mod.qh index 331fdcd85..67080b368 100644 --- a/qcsrc/server/_mod.qh +++ b/qcsrc/server/_mod.qh @@ -5,13 +5,14 @@ #include #include #include -#include -#include -#include +#include +#include +#include #include #include #include #include +#include #include #include #include @@ -24,9 +25,6 @@ #include #include #include -#ifdef SVQC - #include -#endif #include #include diff --git a/qcsrc/server/anticheat.qc b/qcsrc/server/anticheat.qc index c0d1cc10e..7e8e50660 100644 --- a/qcsrc/server/anticheat.qc +++ b/qcsrc/server/anticheat.qc @@ -6,7 +6,7 @@ #include #include #include -#include +#include #include "miscfunctions.qh" #include "command/common.qh" diff --git a/qcsrc/server/bot/default/bot.qc b/qcsrc/server/bot/default/bot.qc index 9d526b74d..e8f810231 100644 --- a/qcsrc/server/bot/default/bot.qc +++ b/qcsrc/server/bot/default/bot.qc @@ -18,8 +18,8 @@ #include "../../client.qh" #include "../../constants.qh" #include -#include -#include +#include +#include #include "../../race.qh" #include diff --git a/qcsrc/server/bot/default/havocbot/havocbot.qc b/qcsrc/server/bot/default/havocbot/havocbot.qc index f6917bdb4..40a07a44c 100644 --- a/qcsrc/server/bot/default/havocbot/havocbot.qc +++ b/qcsrc/server/bot/default/havocbot/havocbot.qc @@ -6,7 +6,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/qcsrc/server/campaign.qc b/qcsrc/server/campaign.qc index 92d183a45..de46e57d5 100644 --- a/qcsrc/server/campaign.qc +++ b/qcsrc/server/campaign.qc @@ -5,7 +5,7 @@ #include "cheats.qh" #include "miscfunctions.qh" -#include "g_world.qh" +#include "world.qh" #include "../common/campaign_common.qh" diff --git a/qcsrc/server/cheats.qc b/qcsrc/server/cheats.qc index dc41fac73..9d5cc1d42 100644 --- a/qcsrc/server/cheats.qc +++ b/qcsrc/server/cheats.qc @@ -5,9 +5,9 @@ #include #include #include -#include +#include -#include "g_damage.qh" +#include "damage.qh" #include "clientkill.qh" #include "player.qh" #include "race.qh" diff --git a/qcsrc/server/client.qc b/qcsrc/server/client.qc index 99aa86d24..0a9a38faf 100644 --- a/qcsrc/server/client.qc +++ b/qcsrc/server/client.qc @@ -13,17 +13,17 @@ #include "teamplay.qh" #include "spawnpoints.qh" #include "resources.qh" -#include "g_damage.qh" +#include "damage.qh" #include "handicap.qh" -#include "g_hook.qh" +#include "hook.qh" #include "command/common.qh" #include "command/vote.qh" #include "clientkill.qh" #include "cheats.qh" -#include "g_world.qh" +#include "world.qh" #include #include "race.qh" -#include +#include #include "antilag.qh" #include "campaign.qh" #include "command/common.qh" diff --git a/qcsrc/server/clientkill.qc b/qcsrc/server/clientkill.qc index 600faed5a..73ccb383d 100644 --- a/qcsrc/server/clientkill.qc +++ b/qcsrc/server/clientkill.qc @@ -5,7 +5,7 @@ #include #include -#include "g_damage.qh" +#include "damage.qh" #include "teamplay.qh" #include diff --git a/qcsrc/server/command/cmd.qc b/qcsrc/server/command/cmd.qc index 815c5e23f..fe92d3667 100644 --- a/qcsrc/server/command/cmd.qc +++ b/qcsrc/server/command/cmd.qc @@ -1,6 +1,6 @@ #include "cmd.qh" -#include +#include #include #include diff --git a/qcsrc/server/command/common.qc b/qcsrc/server/command/common.qc index 48f1cd168..0f59802dd 100644 --- a/qcsrc/server/command/common.qc +++ b/qcsrc/server/command/common.qc @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include #include diff --git a/qcsrc/server/command/common.qh b/qcsrc/server/command/common.qh index ea3af5469..c9aad4b79 100644 --- a/qcsrc/server/command/common.qh +++ b/qcsrc/server/command/common.qh @@ -54,7 +54,7 @@ float timeout_status; // (values: 0, 1, 2) contains whether a timeout is not .float allowed_timeouts; // contains the number of allowed timeouts for each player .vector lastV_angle; // used when pausing the game in order to force the player to keep his old view angle fixed -// allow functions to be used in other code like g_world.qc and teamplay.qc +// allow functions to be used in other code like world.qc and teamplay.qc void timeout_handler_think(entity this); // used by common/command/generic.qc:GenericCommand_dumpcommands to list all commands into a .txt file diff --git a/qcsrc/server/command/getreplies.qc b/qcsrc/server/command/getreplies.qc index c5a26e8c9..326297489 100644 --- a/qcsrc/server/command/getreplies.qc +++ b/qcsrc/server/command/getreplies.qc @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include @@ -22,7 +22,7 @@ // Last updated: December 30th, 2011 // ========================================================= -// These strings are set usually during init in g_world.qc, +// These strings are set usually during init in world.qc, // or also by some game modes or other functions manually, // and their purpose is to output information to clients // without using any extra processing time. diff --git a/qcsrc/server/command/getreplies.qh b/qcsrc/server/command/getreplies.qh index 7b4360792..3ababeec5 100644 --- a/qcsrc/server/command/getreplies.qh +++ b/qcsrc/server/command/getreplies.qh @@ -13,7 +13,7 @@ const int LADDER_SIZE = 30; // ladder shows the top X players string top_uids[LADDER_SIZE]; float top_scores[LADDER_SIZE]; -// allow functions to be used in other code like g_world.qc and race.qc +// allow functions to be used in other code like world.qc and race.qc string getrecords(float page); string getrankings(); string getladder(); diff --git a/qcsrc/server/command/radarmap.qc b/qcsrc/server/command/radarmap.qc index 7ec8eb6ea..6e73777a0 100644 --- a/qcsrc/server/command/radarmap.qc +++ b/qcsrc/server/command/radarmap.qc @@ -4,7 +4,7 @@ #include #include -#include "../g_world.qh" +#include "../world.qh" #include diff --git a/qcsrc/server/command/sv_cmd.qc b/qcsrc/server/command/sv_cmd.qc index 034bd6cb9..7de740bdd 100644 --- a/qcsrc/server/command/sv_cmd.qc +++ b/qcsrc/server/command/sv_cmd.qc @@ -13,7 +13,7 @@ #include "../campaign.qh" #include "../client.qh" #include "../player.qh" -#include "../g_world.qh" +#include "../world.qh" #include "../ipban.qh" #include "../teamplay.qh" diff --git a/qcsrc/server/command/vote.qc b/qcsrc/server/command/vote.qc index 2ad5c0510..ee8a8e8db 100644 --- a/qcsrc/server/command/vote.qc +++ b/qcsrc/server/command/vote.qc @@ -11,8 +11,8 @@ #include "common.qh" -#include "../g_damage.qh" -#include "../g_world.qh" +#include "../damage.qh" +#include "../world.qh" #include "../teamplay.qh" #include "../race.qh" #include "../round_handler.qh" diff --git a/qcsrc/server/command/vote.qh b/qcsrc/server/command/vote.qh index 55068ab46..7085ca0e1 100644 --- a/qcsrc/server/command/vote.qh +++ b/qcsrc/server/command/vote.qh @@ -38,7 +38,7 @@ string vote_called_display; // visual string of command sent by client string vote_parsed_command; // command which is fixed after being parsed string vote_parsed_display; // visual string which is fixed after being parsed -// allow functions to be used in other code like g_world.qc and teamplay.qc +// allow functions to be used in other code like world.qc and teamplay.qc void VoteThink(); void VoteReset(); void VoteCommand(int request, entity caller, int argc, string vote_command); diff --git a/qcsrc/server/damage.qc b/qcsrc/server/damage.qc new file mode 100644 index 000000000..f799e96f7 --- /dev/null +++ b/qcsrc/server/damage.qc @@ -0,0 +1,1301 @@ +#include "damage.qh" + +#include +#include "bot/api.qh" +#include "hook.qh" +#include +#include +#include +#include +#include +#include "teamplay.qh" +#include "scores.qh" +#include "spawnpoints.qh" +#include "../common/state.qh" +#include "../common/physics/player.qh" +#include "resources.qh" +#include "../common/vehicles/all.qh" +#include "../common/items/_mod.qh" +#include "../common/mutators/mutator/waypoints/waypointsprites.qh" +#include "../common/mutators/mutator/instagib/sv_instagib.qh" +#include "../common/mutators/mutator/buffs/buffs.qh" +#include "weapons/accuracy.qh" +#include "weapons/csqcprojectile.qh" +#include "weapons/selection.qh" +#include "../common/constants.qh" +#include "../common/deathtypes/all.qh" +#include +#include +#include "../common/notifications/all.qh" +#include "../common/physics/movetypes/movetypes.qh" +#include "../common/playerstats.qh" +#include "../common/teams.qh" +#include "../common/util.qh" +#include +#include +#include +#include "../lib/csqcmodel/sv_model.qh" +#include "../lib/warpzone/common.qh" + +void UpdateFrags(entity player, int f) +{ + GameRules_scoring_add_team(player, SCORE, f); +} + +void GiveFrags(entity attacker, entity targ, float f, int deathtype, .entity weaponentity) +{ + // TODO route through PlayerScores instead + if(game_stopped) return; + + if(f < 0) + { + if(targ == attacker) + { + // suicide + GameRules_scoring_add(attacker, SUICIDES, 1); + } + else + { + // teamkill + GameRules_scoring_add(attacker, TEAMKILLS, 1); + } + } + else + { + // regular frag + GameRules_scoring_add(attacker, KILLS, 1); + if(!warmup_stage && targ.playerid) + PlayerStats_GameReport_Event_Player(attacker, sprintf("kills-%d", targ.playerid), 1); + } + + GameRules_scoring_add(targ, DEATHS, 1); + + // FIXME fix the mess this is (we have REAL points now!) + if(MUTATOR_CALLHOOK(GiveFragsForKill, attacker, targ, f, deathtype, attacker.(weaponentity))) + f = M_ARGV(2, float); + + attacker.totalfrags += f; + + if(f) + UpdateFrags(attacker, f); +} + +string AppendItemcodes(string s, entity player) +{ + for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) + { + .entity weaponentity = weaponentities[slot]; + int w = player.(weaponentity).m_weapon.m_id; + if(w == 0) + w = player.(weaponentity).cnt; // previous weapon + if(w != 0 || slot == 0) + s = strcat(s, ftos(w)); + } + if(time < STAT(STRENGTH_FINISHED, player)) + s = strcat(s, "S"); + if(time < STAT(INVINCIBLE_FINISHED, player)) + s = strcat(s, "I"); + if(PHYS_INPUT_BUTTON_CHAT(player)) + s = strcat(s, "T"); + // TODO: include these codes as a flag on the item itself + MUTATOR_CALLHOOK(LogDeath_AppendItemCodes, player, s); + s = M_ARGV(1, string); + return s; +} + +void LogDeath(string mode, int deathtype, entity killer, entity killed) +{ + string s; + if(!autocvar_sv_eventlog) + return; + s = strcat(":kill:", mode); + s = strcat(s, ":", ftos(killer.playerid)); + s = strcat(s, ":", ftos(killed.playerid)); + s = strcat(s, ":type=", Deathtype_Name(deathtype)); + s = strcat(s, ":items="); + s = AppendItemcodes(s, killer); + if(killed != killer) + { + s = strcat(s, ":victimitems="); + s = AppendItemcodes(s, killed); + } + GameLogEcho(s); +} + +void Obituary_SpecialDeath( + entity notif_target, + float murder, + int deathtype, + string s1, string s2, string s3, + float f1, float f2, float f3) +{ + if(!DEATH_ISSPECIAL(deathtype)) + { + backtrace("Obituary_SpecialDeath called without a special deathtype?\n"); + return; + } + + entity deathent = REGISTRY_GET(Deathtypes, deathtype - DT_FIRST); + if (!deathent) + { + backtrace("Obituary_SpecialDeath: Could not find deathtype entity!\n"); + return; + } + + if(g_cts && deathtype == DEATH_KILL.m_id) + return; // TODO: somehow put this in CTS gamemode file! + + Notification death_message = (murder) ? deathent.death_msgmurder : deathent.death_msgself; + if(death_message) + { + Send_Notification_WOCOVA( + NOTIF_ONE, + notif_target, + MSG_MULTI, + death_message, + s1, s2, s3, "", + f1, f2, f3, 0 + ); + Send_Notification_WOCOVA( + NOTIF_ALL_EXCEPT, + notif_target, + MSG_INFO, + death_message.nent_msginfo, + s1, s2, s3, "", + f1, f2, f3, 0 + ); + } +} + +float Obituary_WeaponDeath( + entity notif_target, + float murder, + int deathtype, + string s1, string s2, string s3, + float f1, float f2) +{ + Weapon death_weapon = DEATH_WEAPONOF(deathtype); + if (death_weapon == WEP_Null) + return false; + + w_deathtype = deathtype; + Notification death_message = ((murder) ? death_weapon.wr_killmessage(death_weapon) : death_weapon.wr_suicidemessage(death_weapon)); + w_deathtype = false; + + if (death_message) + { + Send_Notification_WOCOVA( + NOTIF_ONE, + notif_target, + MSG_MULTI, + death_message, + s1, s2, s3, "", + f1, f2, 0, 0 + ); + // send the info part to everyone + Send_Notification_WOCOVA( + NOTIF_ALL_EXCEPT, + notif_target, + MSG_INFO, + death_message.nent_msginfo, + s1, s2, s3, "", + f1, f2, 0, 0 + ); + } + else + { + LOG_TRACEF( + "Obituary_WeaponDeath(): ^1Deathtype ^7(%d)^1 has no notification for weapon %s!\n", + deathtype, + death_weapon.netname + ); + } + + return true; +} + +bool frag_centermessage_override(entity attacker, entity targ, int deathtype, int kill_count_to_attacker, int kill_count_to_target, string attacker_name) +{ + if(deathtype == DEATH_FIRE.m_id) + { + Send_Notification(NOTIF_ONE, attacker, MSG_CHOICE, CHOICE_FRAG_FIRE, targ.netname, kill_count_to_attacker, (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping)); + Send_Notification(NOTIF_ONE, targ, MSG_CHOICE, CHOICE_FRAGGED_FIRE, attacker_name, kill_count_to_target, GetResource(attacker, RES_HEALTH), GetResource(attacker, RES_ARMOR), (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping)); + return true; + } + + return MUTATOR_CALLHOOK(FragCenterMessage, attacker, targ, deathtype, kill_count_to_attacker, kill_count_to_target); +} + +void Obituary(entity attacker, entity inflictor, entity targ, int deathtype, .entity weaponentity) +{ + // Sanity check + if (!IS_PLAYER(targ)) { backtrace("Obituary called on non-player?!\n"); return; } + + // Declarations + float notif_firstblood = false; + float kill_count_to_attacker, kill_count_to_target; + bool notif_anonymous = false; + string attacker_name = attacker.netname; + + // Set final information for the death + targ.death_origin = targ.origin; + string deathlocation = (autocvar_notification_server_allows_location ? NearestLocation(targ.death_origin) : ""); + + // Abort now if a mutator requests it + if (MUTATOR_CALLHOOK(ClientObituary, inflictor, attacker, targ, deathtype, attacker.(weaponentity))) { CS(targ).killcount = 0; return; } + notif_anonymous = M_ARGV(5, bool); + + if(notif_anonymous) + attacker_name = "Anonymous player"; + + #ifdef NOTIFICATIONS_DEBUG + Debug_Notification( + sprintf( + "Obituary(%s, %s, %s, %s = %d);\n", + attacker_name, + inflictor.netname, + targ.netname, + Deathtype_Name(deathtype), + deathtype + ) + ); + #endif + + // ======= + // SUICIDE + // ======= + if(targ == attacker) + { + if(DEATH_ISSPECIAL(deathtype)) + { + if(deathtype == DEATH_TEAMCHANGE.m_id || deathtype == DEATH_AUTOTEAMCHANGE.m_id) + { + Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", targ.team, 0, 0); + } + else + { + switch(DEATH_ENT(deathtype)) + { + case DEATH_MIRRORDAMAGE: + { + Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0, 0); + break; + } + + default: + { + Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0, 0); + break; + } + } + } + } + else if (!Obituary_WeaponDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0)) + { + backtrace("SUICIDE: what the hell happened here?\n"); + return; + } + LogDeath("suicide", deathtype, targ, targ); + if(deathtype != DEATH_AUTOTEAMCHANGE.m_id) // special case: don't negate frags if auto switched + GiveFrags(attacker, targ, -1, deathtype, weaponentity); + } + + // ====== + // MURDER + // ====== + else if(IS_PLAYER(attacker)) + { + if(SAME_TEAM(attacker, targ)) + { + LogDeath("tk", deathtype, attacker, targ); + GiveFrags(attacker, targ, -1, deathtype, weaponentity); + + CS(attacker).killcount = 0; + + Send_Notification(NOTIF_ONE, attacker, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAG, targ.netname); + Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAGGED, attacker_name); + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(targ.team, INFO_DEATH_TEAMKILL), targ.netname, attacker_name, deathlocation, CS(targ).killcount); + + // In this case, the death message will ALWAYS be "foo was betrayed by bar" + // No need for specific death/weapon messages... + } + else + { + LogDeath("frag", deathtype, attacker, targ); + GiveFrags(attacker, targ, 1, deathtype, weaponentity); + + CS(attacker).taunt_soundtime = time + 1; + CS(attacker).killcount = CS(attacker).killcount + 1; + + attacker.killsound += 1; + + // TODO: improve SPREE_ITEM and KILL_SPREE_LIST + // these 2 macros are spread over multiple files + #define SPREE_ITEM(counta,countb,center,normal,gentle) \ + case counta: \ + Send_Notification(NOTIF_ONE, attacker, MSG_ANNCE, ANNCE_KILLSTREAK_##countb); \ + if (!warmup_stage) \ + PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_##counta, 1); \ + break; + + switch(CS(attacker).killcount) + { + KILL_SPREE_LIST + default: break; + } + #undef SPREE_ITEM + + if(!warmup_stage && !checkrules_firstblood) + { + checkrules_firstblood = true; + notif_firstblood = true; // modify the current messages so that they too show firstblood information + PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_FIRSTBLOOD, 1); + PlayerStats_GameReport_Event_Player(targ, PLAYERSTATS_ACHIEVEMENT_FIRSTVICTIM, 1); + + // tell spree_inf and spree_cen that this is a first-blood and first-victim event + kill_count_to_attacker = -1; + kill_count_to_target = -2; + } + else + { + kill_count_to_attacker = CS(attacker).killcount; + kill_count_to_target = 0; + } + + if(targ.istypefrag) + { + Send_Notification( + NOTIF_ONE, + attacker, + MSG_CHOICE, + CHOICE_TYPEFRAG, + targ.netname, + kill_count_to_attacker, + (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping) + ); + Send_Notification( + NOTIF_ONE, + targ, + MSG_CHOICE, + CHOICE_TYPEFRAGGED, + attacker_name, + kill_count_to_target, + GetResource(attacker, RES_HEALTH), + GetResource(attacker, RES_ARMOR), + (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping) + ); + } + else if(!frag_centermessage_override(attacker, targ, deathtype, kill_count_to_attacker, kill_count_to_target, attacker_name)) + { + Send_Notification( + NOTIF_ONE, + attacker, + MSG_CHOICE, + CHOICE_FRAG, + targ.netname, + kill_count_to_attacker, + (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping) + ); + Send_Notification( + NOTIF_ONE, + targ, + MSG_CHOICE, + CHOICE_FRAGGED, + attacker_name, + kill_count_to_target, + GetResource(attacker, RES_HEALTH), + GetResource(attacker, RES_ARMOR), + (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping) + ); + } + + int f3 = 0; + if(deathtype == DEATH_BUFF.m_id) + f3 = buff_FirstFromFlags(STAT(BUFFS, attacker)).m_id; + + if (!Obituary_WeaponDeath(targ, true, deathtype, targ.netname, attacker_name, deathlocation, CS(targ).killcount, kill_count_to_attacker)) + Obituary_SpecialDeath(targ, true, deathtype, targ.netname, attacker_name, deathlocation, CS(targ).killcount, kill_count_to_attacker, f3); + } + } + + // ============= + // ACCIDENT/TRAP + // ============= + else + { + switch(DEATH_ENT(deathtype)) + { + // For now, we're just forcing HURTTRIGGER to behave as "DEATH_VOID" and giving it no special options... + // Later on you will only be able to make custom messages using DEATH_CUSTOM, + // and there will be a REAL DEATH_VOID implementation which mappers will use. + case DEATH_HURTTRIGGER: + { + Obituary_SpecialDeath(targ, false, deathtype, + targ.netname, + inflictor.message, + deathlocation, + CS(targ).killcount, + 0, + 0); + break; + } + + case DEATH_CUSTOM: + { + Obituary_SpecialDeath(targ, false, deathtype, + targ.netname, + ((strstrofs(deathmessage, "%", 0) < 0) ? strcat("%s ", deathmessage) : deathmessage), + deathlocation, + CS(targ).killcount, + 0, + 0); + break; + } + + default: + { + Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0, 0); + break; + } + } + + LogDeath("accident", deathtype, targ, targ); + GiveFrags(targ, targ, -1, deathtype, weaponentity); + + if(GameRules_scoring_add(targ, SCORE, 0) == -5) + { + Send_Notification(NOTIF_ONE, targ, MSG_ANNCE, ANNCE_ACHIEVEMENT_BOTLIKE); + if (!warmup_stage) + { + PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_BOTLIKE, 1); + } + } + } + + // reset target kill count + CS(targ).killcount = 0; +} + +void Ice_Think(entity this) +{ + if(!STAT(FROZEN, this.owner) || this.owner.iceblock != this) + { + delete(this); + return; + } + vector ice_org = this.owner.origin - '0 0 16'; + if (this.origin != ice_org) + setorigin(this, ice_org); + this.nextthink = time; +} + +void Freeze(entity targ, float revivespeed, int frozen_type, bool show_waypoint) +{ + if(!IS_PLAYER(targ) && !IS_MONSTER(targ)) // TODO: only specified entities can be freezed + return; + + if(STAT(FROZEN, targ)) + return; + + float targ_maxhealth = ((IS_MONSTER(targ)) ? targ.max_health : start_health); + + STAT(FROZEN, targ) = frozen_type; + STAT(REVIVE_PROGRESS, targ) = ((frozen_type == FROZEN_TEMP_DYING) ? 1 : 0); + SetResource(targ, RES_HEALTH, ((frozen_type == FROZEN_TEMP_DYING) ? targ_maxhealth : 1)); + targ.revive_speed = revivespeed; + if(targ.bot_attack) + IL_REMOVE(g_bot_targets, targ); + targ.bot_attack = false; + targ.freeze_time = time; + + entity ice = new(ice); + ice.owner = targ; + ice.scale = targ.scale; + // set_movetype(ice, MOVETYPE_FOLLOW) would rotate the ice model with the player + setthink(ice, Ice_Think); + ice.nextthink = time; + ice.frame = floor(random() * 21); // ice model has 20 different looking frames + setmodel(ice, MDL_ICE); + ice.alpha = 1; + ice.colormod = Team_ColorRGB(targ.team); + ice.glowmod = ice.colormod; + targ.iceblock = ice; + targ.revival_time = 0; + + Ice_Think(ice); + + RemoveGrapplingHooks(targ); + + FOREACH_CLIENT(IS_PLAYER(it), + { + for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) + { + .entity weaponentity = weaponentities[slot]; + if(it.(weaponentity).hook.aiment == targ) + RemoveHook(it.(weaponentity).hook); + } + }); + + // add waypoint + if(MUTATOR_CALLHOOK(Freeze, targ, revivespeed, frozen_type) || show_waypoint) + WaypointSprite_Spawn(WP_Frozen, 0, 0, targ, '0 0 64', NULL, targ.team, targ, waypointsprite_attached, true, RADARICON_WAYPOINT); +} + +void Unfreeze(entity targ, bool reset_health) +{ + if(!STAT(FROZEN, targ)) + return; + + if (reset_health && STAT(FROZEN, targ) != FROZEN_TEMP_DYING) + SetResource(targ, RES_HEALTH, ((IS_PLAYER(targ)) ? start_health : targ.max_health)); + + targ.pauseregen_finished = time + autocvar_g_balance_pause_health_regen; + + STAT(FROZEN, targ) = 0; + STAT(REVIVE_PROGRESS, targ) = 0; + targ.revival_time = time; + if(!targ.bot_attack) + IL_PUSH(g_bot_targets, targ); + targ.bot_attack = true; + + WaypointSprite_Kill(targ.waypointsprite_attached); + + FOREACH_CLIENT(IS_PLAYER(it), + { + for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) + { + .entity weaponentity = weaponentities[slot]; + if(it.(weaponentity).hook.aiment == targ) + RemoveHook(it.(weaponentity).hook); + } + }); + + // remove the ice block + if(targ.iceblock) + delete(targ.iceblock); + targ.iceblock = NULL; + + MUTATOR_CALLHOOK(Unfreeze, targ); +} + +void Damage(entity targ, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force) +{ + float complainteamdamage = 0; + float mirrordamage = 0; + float mirrorforce = 0; + + if (game_stopped || (IS_CLIENT(targ) && CS(targ).killcount == FRAGS_SPECTATOR)) + return; + + entity attacker_save = attacker; + + // special rule: gravity bombs and sound-based attacks do not affect team mates (other than for disconnecting the hook) + if(DEATH_ISWEAPON(deathtype, WEP_HOOK) || (deathtype & HITTYPE_SOUND)) + { + if(IS_PLAYER(targ) && SAME_TEAM(targ, attacker)) + { + return; + } + } + + if(deathtype == DEATH_KILL.m_id || deathtype == DEATH_TEAMCHANGE.m_id || deathtype == DEATH_AUTOTEAMCHANGE.m_id) + { + // exit the vehicle before killing (fixes a crash) + if(IS_PLAYER(targ) && targ.vehicle) + vehicles_exit(targ.vehicle, VHEF_RELEASE); + + // These are ALWAYS lethal + // No damage modification here + // Instead, prepare the victim for his death... + SetResourceExplicit(targ, RES_ARMOR, 0); + targ.spawnshieldtime = 0; + SetResourceExplicit(targ, RES_HEALTH, 0.9); // this is < 1 + targ.flags -= targ.flags & FL_GODMODE; + damage = 100000; + } + else if(deathtype == DEATH_MIRRORDAMAGE.m_id || deathtype == DEATH_NOAMMO.m_id) + { + // no processing + } + else + { + // nullify damage if teamplay is on + if(deathtype != DEATH_TELEFRAG.m_id) + if(IS_PLAYER(attacker)) + { + if(IS_PLAYER(targ) && targ != attacker && (IS_INDEPENDENT_PLAYER(attacker) || IS_INDEPENDENT_PLAYER(targ))) + { + damage = 0; + force = '0 0 0'; + } + else if(SAME_TEAM(attacker, targ)) + { + if(autocvar_teamplay_mode == 1) + damage = 0; + else if(attacker != targ) + { + if(autocvar_teamplay_mode == 2) + { + if(IS_PLAYER(targ) && !IS_DEAD(targ)) + { + attacker.dmg_team = attacker.dmg_team + damage; + complainteamdamage = attacker.dmg_team - autocvar_g_teamdamage_threshold; + } + } + else if(autocvar_teamplay_mode == 3) + damage = 0; + else if(autocvar_teamplay_mode == 4) + { + if(IS_PLAYER(targ) && !IS_DEAD(targ)) + { + attacker.dmg_team = attacker.dmg_team + damage; + complainteamdamage = attacker.dmg_team - autocvar_g_teamdamage_threshold; + if(complainteamdamage > 0) + mirrordamage = autocvar_g_mirrordamage * complainteamdamage; + mirrorforce = autocvar_g_mirrordamage * vlen(force); + damage = autocvar_g_friendlyfire * damage; + // mirrordamage will be used LATER + + if(autocvar_g_mirrordamage_virtual) + { + vector v = healtharmor_applydamage(GetResource(attacker, RES_ARMOR), autocvar_g_balance_armor_blockpercent, deathtype, mirrordamage); + attacker.dmg_take += v.x; + attacker.dmg_save += v.y; + attacker.dmg_inflictor = inflictor; + mirrordamage = v.z; + mirrorforce = 0; + } + + if(autocvar_g_friendlyfire_virtual) + { + vector v = healtharmor_applydamage(GetResource(targ, RES_ARMOR), autocvar_g_balance_armor_blockpercent, deathtype, damage); + targ.dmg_take += v.x; + targ.dmg_save += v.y; + targ.dmg_inflictor = inflictor; + damage = 0; + if(!autocvar_g_friendlyfire_virtual_force) + force = '0 0 0'; + } + } + else if(!targ.canteamdamage) + damage = 0; + } + } + } + } + + if (!DEATH_ISSPECIAL(deathtype)) + { + damage *= g_weapondamagefactor; + mirrordamage *= g_weapondamagefactor; + complainteamdamage *= g_weapondamagefactor; + force = force * g_weaponforcefactor; + mirrorforce *= g_weaponforcefactor; + } + + // should this be changed at all? If so, in what way? + MUTATOR_CALLHOOK(Damage_Calculate, inflictor, attacker, targ, deathtype, damage, mirrordamage, force, attacker.(weaponentity)); + damage = M_ARGV(4, float); + mirrordamage = M_ARGV(5, float); + force = M_ARGV(6, vector); + + if(IS_PLAYER(targ) && damage > 0 && attacker) + { + for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) + { + .entity went = weaponentities[slot]; + if(targ.(went).hook && targ.(went).hook.aiment == attacker) + RemoveHook(targ.(went).hook); + } + } + + if(STAT(FROZEN, targ) && !ITEM_DAMAGE_NEEDKILL(deathtype) + && deathtype != DEATH_TEAMCHANGE.m_id && deathtype != DEATH_AUTOTEAMCHANGE.m_id) + { + if(autocvar_g_frozen_revive_falldamage > 0 && deathtype == DEATH_FALL.m_id && damage >= autocvar_g_frozen_revive_falldamage) + { + Unfreeze(targ, false); + SetResource(targ, RES_HEALTH, autocvar_g_frozen_revive_falldamage_health); + Send_Effect(EFFECT_ICEORGLASS, targ.origin, '0 0 0', 3); + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_REVIVED_FALL, targ.netname); + Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_FREEZETAG_REVIVE_SELF); + } + + damage = 0; + force *= autocvar_g_frozen_force; + } + + if(IS_PLAYER(targ) && STAT(FROZEN, targ) + && ITEM_DAMAGE_NEEDKILL(deathtype) && !autocvar_g_frozen_damage_trigger) + { + Send_Effect(EFFECT_TELEPORT, targ.origin, '0 0 0', 1); + + entity spot = SelectSpawnPoint(targ, false); + if(spot) + { + damage = 0; + targ.deadflag = DEAD_NO; + + targ.angles = spot.angles; + + targ.effects = 0; + targ.effects |= EF_TELEPORT_BIT; + + targ.angles_z = 0; // never spawn tilted even if the spot says to + targ.fixangle = true; // turn this way immediately + targ.velocity = '0 0 0'; + targ.avelocity = '0 0 0'; + targ.punchangle = '0 0 0'; + targ.punchvector = '0 0 0'; + targ.oldvelocity = targ.velocity; + + targ.spawnorigin = spot.origin; + setorigin(targ, spot.origin + '0 0 1' * (1 - targ.mins.z - 24)); + // don't reset back to last position, even if new position is stuck in solid + targ.oldorigin = targ.origin; + + Send_Effect(EFFECT_TELEPORT, targ.origin, '0 0 0', 1); + } + } + + if(!MUTATOR_IS_ENABLED(mutator_instagib)) + { + // apply strength multiplier + if (attacker.items & ITEM_Strength.m_itemid) + { + if(targ == attacker) + { + damage = damage * autocvar_g_balance_powerup_strength_selfdamage; + force = force * autocvar_g_balance_powerup_strength_selfforce; + } + else + { + damage = damage * autocvar_g_balance_powerup_strength_damage; + force = force * autocvar_g_balance_powerup_strength_force; + } + } + + // apply invincibility multiplier + if (targ.items & ITEM_Shield.m_itemid) + { + damage = damage * autocvar_g_balance_powerup_invincible_takedamage; + if (targ != attacker) + { + force = force * autocvar_g_balance_powerup_invincible_takeforce; + } + } + } + + if (targ == attacker) + damage = damage * autocvar_g_balance_selfdamagepercent; // Partial damage if the attacker hits himself + + // count the damage + if(attacker) + if(!IS_DEAD(targ)) + if(deathtype != DEATH_BUFF.m_id) + if(targ.takedamage == DAMAGE_AIM) + if(targ != attacker) + { + entity victim; + if(IS_VEHICLE(targ) && targ.owner) + victim = targ.owner; + else + victim = targ; + + if(IS_PLAYER(victim) || (IS_TURRET(victim) && victim.active == ACTIVE_ACTIVE) || IS_MONSTER(victim) || MUTATOR_CALLHOOK(PlayHitsound, victim, attacker)) + { + if(DIFF_TEAM(victim, attacker) && !STAT(FROZEN, victim)) + { + if(damage > 0) + { + if(deathtype != DEATH_FIRE.m_id) + { + if(PHYS_INPUT_BUTTON_CHAT(victim)) + attacker.typehitsound += 1; + else + attacker.damage_dealt += damage; + } + + damage_goodhits += 1; + damage_gooddamage += damage; + + if (!DEATH_ISSPECIAL(deathtype)) + { + if(IS_PLAYER(targ)) // don't do this for vehicles + if(IsFlying(victim)) + yoda = 1; + } + } + } + else if(IS_PLAYER(attacker)) + { + // if enemy gets frozen in this frame and receives other damage don't + // play the typehitsound e.g. when hit by multiple bullets of the shotgun + if (deathtype != DEATH_FIRE.m_id && (!STAT(FROZEN, victim) || time > victim.freeze_time)) + { + attacker.typehitsound += 1; + } + if(complainteamdamage > 0) + if(time > CS(attacker).teamkill_complain) + { + CS(attacker).teamkill_complain = time + 5; + CS(attacker).teamkill_soundtime = time + 0.4; + CS(attacker).teamkill_soundsource = targ; + } + } + } + } + } + + // apply push + if (targ.damageforcescale) + if (force) + if (!IS_PLAYER(targ) || time >= targ.spawnshieldtime || targ == attacker) + { + vector farce = damage_explosion_calcpush(targ.damageforcescale * force, targ.velocity, autocvar_g_balance_damagepush_speedfactor); + if(targ.move_movetype == MOVETYPE_PHYSICS) + { + entity farcent = new(farce); + farcent.enemy = targ; + farcent.movedir = farce * 10; + if(targ.mass) + farcent.movedir = farcent.movedir * targ.mass; + farcent.origin = hitloc; + farcent.forcetype = FORCETYPE_FORCEATPOS; + farcent.nextthink = time + 0.1; + setthink(farcent, SUB_Remove); + } + else if(targ.move_movetype != MOVETYPE_NOCLIP) + { + targ.velocity = targ.velocity + farce; + } + UNSET_ONGROUND(targ); + UpdateCSQCProjectile(targ); + } + // apply damage + if (damage != 0 || (targ.damageforcescale && force)) + if (targ.event_damage) + targ.event_damage (targ, inflictor, attacker, damage, deathtype, weaponentity, hitloc, force); + + // apply mirror damage if any + if(!autocvar_g_mirrordamage_onlyweapons || DEATH_WEAPONOF(deathtype) != WEP_Null) + if(mirrordamage > 0 || mirrorforce > 0) + { + attacker = attacker_save; + + force = normalize(attacker.origin + attacker.view_ofs - hitloc) * mirrorforce; + Damage(attacker, inflictor, attacker, mirrordamage, DEATH_MIRRORDAMAGE.m_id, weaponentity, attacker.origin, force); + } +} + +float RadiusDamageForSource (entity inflictor, vector inflictororigin, vector inflictorvelocity, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe, + float inflictorselfdamage, float forceintensity, float forcezscale, int deathtype, .entity weaponentity, entity directhitentity) + // Returns total damage applies to creatures +{ + entity targ; + vector force; + float total_damage_to_creatures; + entity next; + float tfloordmg; + float tfloorforce; + + float stat_damagedone; + + if(RadiusDamage_running) + { + backtrace("RadiusDamage called recursively! Expect stuff to go HORRIBLY wrong."); + return 0; + } + + RadiusDamage_running = 1; + + tfloordmg = autocvar_g_throughfloor_damage; + tfloorforce = autocvar_g_throughfloor_force; + + total_damage_to_creatures = 0; + + if(deathtype != (WEP_HOOK.m_id | HITTYPE_SECONDARY | HITTYPE_BOUNCE)) // only send gravity bomb damage once + if(!(deathtype & HITTYPE_SOUND)) // do not send radial sound damage (bandwidth hog) + { + force = inflictorvelocity; + if(force == '0 0 0') + force = '0 0 -1'; + else + force = normalize(force); + if(forceintensity >= 0) + Damage_DamageInfo(inflictororigin, coredamage, edgedamage, rad, forceintensity * force, deathtype, 0, attacker); + else + Damage_DamageInfo(inflictororigin, coredamage, edgedamage, -rad, (-forceintensity) * force, deathtype, 0, attacker); + } + + stat_damagedone = 0; + + targ = WarpZone_FindRadius (inflictororigin, rad + MAX_DAMAGEEXTRARADIUS, false); + while (targ) + { + next = targ.chain; + if ((targ != inflictor) || inflictorselfdamage) + if (((cantbe != targ) && !mustbe) || (mustbe == targ)) + if (targ.takedamage) + { + vector nearest; + vector diff; + float power; + + // LordHavoc: measure distance to nearest point on target (not origin) + // (this guarentees 100% damage on a touch impact) + nearest = targ.WarpZone_findradius_nearest; + diff = targ.WarpZone_findradius_dist; + // round up a little on the damage to ensure full damage on impacts + // and turn the distance into a fraction of the radius + power = 1 - ((vlen (diff) - bound(MIN_DAMAGEEXTRARADIUS, targ.damageextraradius, MAX_DAMAGEEXTRARADIUS)) / rad); + //bprint(" "); + //bprint(ftos(power)); + //if (targ == attacker) + // print(ftos(power), "\n"); + if (power > 0) + { + float finaldmg; + if (power > 1) + power = 1; + finaldmg = coredamage * power + edgedamage * (1 - power); + if (finaldmg > 0) + { + float a; + float c; + vector hitloc; + vector myblastorigin; + vector center; + + myblastorigin = WarpZone_TransformOrigin(targ, inflictororigin); + + // if it's a player, use the view origin as reference + center = CENTER_OR_VIEWOFS(targ); + + force = normalize(center - myblastorigin); + force = force * (finaldmg / coredamage) * forceintensity; + hitloc = nearest; + + // apply special scaling along the z axis if set + // NOTE: 0 value is not allowed for compatibility, in the case of weapon cvars not being set + if(forcezscale) + force.z *= forcezscale; + + if(targ != directhitentity) + { + float hits; + float total; + float hitratio; + float mininv_f, mininv_d; + + // test line of sight to multiple positions on box, + // and do damage if any of them hit + hits = 0; + + // we know: max stddev of hitratio = 1 / (2 * sqrt(n)) + // so for a given max stddev: + // n = (1 / (2 * max stddev of hitratio))^2 + + mininv_d = (finaldmg * (1-tfloordmg)) / autocvar_g_throughfloor_damage_max_stddev; + mininv_f = (vlen(force) * (1-tfloorforce)) / autocvar_g_throughfloor_force_max_stddev; + + if(autocvar_g_throughfloor_debug) + LOG_INFOF("THROUGHFLOOR: D=%f F=%f max(dD)=1/%f max(dF)=1/%f", finaldmg, vlen(force), mininv_d, mininv_f); + + + total = 0.25 * (max(mininv_f, mininv_d) ** 2); + + if(autocvar_g_throughfloor_debug) + LOG_INFOF(" steps=%f", total); + + + if (IS_PLAYER(targ)) + total = ceil(bound(autocvar_g_throughfloor_min_steps_player, total, autocvar_g_throughfloor_max_steps_player)); + else + total = ceil(bound(autocvar_g_throughfloor_min_steps_other, total, autocvar_g_throughfloor_max_steps_other)); + + if(autocvar_g_throughfloor_debug) + LOG_INFOF(" steps=%f dD=%f dF=%f", total, finaldmg * (1-tfloordmg) / (2 * sqrt(total)), vlen(force) * (1-tfloorforce) / (2 * sqrt(total))); + + for(c = 0; c < total; ++c) + { + //traceline(targ.WarpZone_findradius_findorigin, nearest, MOVE_NOMONSTERS, inflictor); + WarpZone_TraceLine(inflictororigin, WarpZone_UnTransformOrigin(targ, nearest), MOVE_NOMONSTERS, inflictor); + if (trace_fraction == 1 || trace_ent == targ) + { + ++hits; + if (hits > 1) + hitloc = hitloc + nearest; + else + hitloc = nearest; + } + nearest.x = targ.origin.x + targ.mins.x + random() * targ.size.x; + nearest.y = targ.origin.y + targ.mins.y + random() * targ.size.y; + nearest.z = targ.origin.z + targ.mins.z + random() * targ.size.z; + } + + nearest = hitloc * (1 / max(1, hits)); + hitratio = (hits / total); + a = bound(0, tfloordmg + (1-tfloordmg) * hitratio, 1); + finaldmg = finaldmg * a; + a = bound(0, tfloorforce + (1-tfloorforce) * hitratio, 1); + force = force * a; + + if(autocvar_g_throughfloor_debug) + LOG_INFOF(" D=%f F=%f", finaldmg, vlen(force)); + } + + //if (targ == attacker) + //{ + // print("hits ", ftos(hits), " / ", ftos(total)); + // print(" finaldmg ", ftos(finaldmg), " force ", vtos(force)); + // print(" (", ftos(a), ")\n"); + //} + if(finaldmg || force) + { + if(targ.iscreature) + { + total_damage_to_creatures += finaldmg; + + if(accuracy_isgooddamage(attacker, targ)) + stat_damagedone += finaldmg; + } + + if(targ == directhitentity || DEATH_ISSPECIAL(deathtype)) + Damage(targ, inflictor, attacker, finaldmg, deathtype, weaponentity, nearest, force); + else + Damage(targ, inflictor, attacker, finaldmg, deathtype | HITTYPE_SPLASH, weaponentity, nearest, force); + } + } + } + } + targ = next; + } + + RadiusDamage_running = 0; + + if(!DEATH_ISSPECIAL(deathtype)) + accuracy_add(attacker, DEATH_WEAPONOF(deathtype), 0, min(coredamage, stat_damagedone)); + + return total_damage_to_creatures; +} + +float RadiusDamage(entity inflictor, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe, float forceintensity, int deathtype, .entity weaponentity, entity directhitentity) +{ + return RadiusDamageForSource(inflictor, (inflictor.origin + (inflictor.mins + inflictor.maxs) * 0.5), inflictor.velocity, attacker, coredamage, edgedamage, rad, + cantbe, mustbe, false, forceintensity, 1, deathtype, weaponentity, directhitentity); +} + +bool Heal(entity targ, entity inflictor, float amount, float limit) +{ + if(game_stopped || (IS_CLIENT(targ) && CS(targ).killcount == FRAGS_SPECTATOR) || STAT(FROZEN, targ) || IS_DEAD(targ)) + return false; + + bool healed = false; + if(targ.event_heal) + healed = targ.event_heal(targ, inflictor, amount, limit); + // TODO: additional handling? what if the healing kills them? should this abort if healing would do so etc + // TODO: healing fx! + // TODO: armor healing? + return healed; +} + +float Fire_IsBurning(entity e) +{ + return (time < e.fire_endtime); +} + +float Fire_AddDamage(entity e, entity o, float d, float t, float dt) +{ + float dps; + float maxtime, mintime, maxdamage, mindamage, maxdps, mindps, totaldamage, totaltime; + + if(IS_PLAYER(e)) + { + if(IS_DEAD(e)) + return -1; + } + else + { + if(!e.fire_burner) + { + // print("adding a fire burner to ", e.classname, "\n"); + e.fire_burner = new(fireburner); + setthink(e.fire_burner, fireburner_think); + e.fire_burner.nextthink = time; + e.fire_burner.owner = e; + } + } + + t = max(t, 0.1); + dps = d / t; + if(Fire_IsBurning(e)) + { + mintime = e.fire_endtime - time; + maxtime = max(mintime, t); + + mindps = e.fire_damagepersec; + maxdps = max(mindps, dps); + + if(maxtime > mintime || maxdps > mindps) + { + // Constraints: + + // damage we have right now + mindamage = mindps * mintime; + + // damage we want to get + maxdamage = mindamage + d; + + // but we can't exceed maxtime * maxdps! + totaldamage = min(maxdamage, maxtime * maxdps); + + // LEMMA: + // Look at: + // totaldamage = min(mindamage + d, maxtime * maxdps) + // We see: + // totaldamage <= maxtime * maxdps + // ==> totaldamage / maxdps <= maxtime. + // We also see: + // totaldamage / mindps = min(mindamage / mindps + d, maxtime * maxdps / mindps) + // >= min(mintime, maxtime) + // ==> totaldamage / maxdps >= mintime. + + /* + // how long do we damage then? + // at least as long as before + // but, never exceed maxdps + totaltime = max(mintime, totaldamage / maxdps); // always <= maxtime due to lemma + */ + + // alternate: + // at most as long as maximum allowed + // but, never below mindps + totaltime = min(maxtime, totaldamage / mindps); // always >= mintime due to lemma + + // assuming t > mintime, dps > mindps: + // we get d = t * dps = maxtime * maxdps + // totaldamage = min(maxdamage, maxtime * maxdps) = min(... + d, maxtime * maxdps) = maxtime * maxdps + // totaldamage / maxdps = maxtime + // totaldamage / mindps > totaldamage / maxdps = maxtime + // FROM THIS: + // a) totaltime = max(mintime, maxtime) = maxtime + // b) totaltime = min(maxtime, totaldamage / maxdps) = maxtime + + // assuming t <= mintime: + // we get maxtime = mintime + // a) totaltime = max(mintime, ...) >= mintime, also totaltime <= maxtime by the lemma, therefore totaltime = mintime = maxtime + // b) totaltime = min(maxtime, ...) <= maxtime, also totaltime >= mintime by the lemma, therefore totaltime = mintime = maxtime + + // assuming dps <= mindps: + // we get mindps = maxdps. + // With this, the lemma says that mintime <= totaldamage / mindps = totaldamage / maxdps <= maxtime. + // a) totaltime = max(mintime, totaldamage / maxdps) = totaldamage / maxdps + // b) totaltime = min(maxtime, totaldamage / mindps) = totaldamage / maxdps + + e.fire_damagepersec = totaldamage / totaltime; + e.fire_endtime = time + totaltime; + if(totaldamage > 1.2 * mindamage) + { + e.fire_deathtype = dt; + if(e.fire_owner != o) + { + e.fire_owner = o; + e.fire_hitsound = false; + } + } + if(accuracy_isgooddamage(o, e)) + accuracy_add(o, DEATH_WEAPONOF(dt), 0, max(0, totaldamage - mindamage)); + return max(0, totaldamage - mindamage); // can never be negative, but to make sure + } + else + return 0; + } + else + { + e.fire_damagepersec = dps; + e.fire_endtime = time + t; + e.fire_deathtype = dt; + e.fire_owner = o; + e.fire_hitsound = false; + if(accuracy_isgooddamage(o, e)) + accuracy_add(o, DEATH_WEAPONOF(dt), 0, d); + return d; + } +} + +void Fire_ApplyDamage(entity e) +{ + float t, d, hi, ty; + entity o; + + if (!Fire_IsBurning(e)) + return; + + for(t = 0, o = e.owner; o.owner && t < 16; o = o.owner, ++t); + if(IS_NOT_A_CLIENT(o)) + o = e.fire_owner; + + // water and slime stop fire + if(e.waterlevel) + if(e.watertype != CONTENT_LAVA) + e.fire_endtime = 0; + + // ice stops fire + if(STAT(FROZEN, e)) + e.fire_endtime = 0; + + t = min(frametime, e.fire_endtime - time); + d = e.fire_damagepersec * t; + + hi = e.fire_owner.damage_dealt; + ty = e.fire_owner.typehitsound; + Damage(e, e, e.fire_owner, d, e.fire_deathtype, DMG_NOWEP, e.origin, '0 0 0'); + if(e.fire_hitsound && e.fire_owner) + { + e.fire_owner.damage_dealt = hi; + e.fire_owner.typehitsound = ty; + } + e.fire_hitsound = true; + + if(!IS_INDEPENDENT_PLAYER(e) && !STAT(FROZEN, e)) + { + IL_EACH(g_damagedbycontents, it.damagedbycontents && it != e, + { + if(!IS_DEAD(it) && it.takedamage && !IS_INDEPENDENT_PLAYER(it)) + if(boxesoverlap(e.absmin, e.absmax, it.absmin, it.absmax)) + { + t = autocvar_g_balance_firetransfer_time * (e.fire_endtime - time); + d = autocvar_g_balance_firetransfer_damage * e.fire_damagepersec * t; + Fire_AddDamage(it, o, d, t, DEATH_FIRE.m_id); + } + }); + } +} + +void Fire_ApplyEffect(entity e) +{ + if(Fire_IsBurning(e)) + e.effects |= EF_FLAME; + else + e.effects &= ~EF_FLAME; +} + +void fireburner_think(entity this) +{ + // for players, this is done in the regular loop + if(wasfreed(this.owner)) + { + delete(this); + return; + } + Fire_ApplyEffect(this.owner); + if(!Fire_IsBurning(this.owner)) + { + this.owner.fire_burner = NULL; + delete(this); + return; + } + Fire_ApplyDamage(this.owner); + this.nextthink = time; +} diff --git a/qcsrc/server/damage.qh b/qcsrc/server/damage.qh new file mode 100644 index 000000000..14dbaf074 --- /dev/null +++ b/qcsrc/server/damage.qh @@ -0,0 +1,163 @@ +#pragma once + +#if defined(CSQC) +#elif defined(MENUQC) +#elif defined(SVQC) + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include "weapons/accuracy.qh" + #include "weapons/csqcprojectile.qh" + #include "weapons/selection.qh" + #include "autocvars.qh" + #include "constants.qh" + #include + #include + #include + #include + #include + #include + #include + #include "hook.qh" + #include "scores.qh" + #include "spawnpoints.qh" +#endif + +.void(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force) event_damage; + +.bool(entity targ, entity inflictor, float amount, float limit) event_heal; + +.float dmg; +.float dmg_edge; +.float dmg_force; +.float dmg_radius; + +bool Damage_DamageInfo_SendEntity(entity this, entity to, int sf); + +void Damage_DamageInfo(vector org, float coredamage, float edgedamage, float rad, vector force, int deathtype, float bloodtype, entity dmgowner); + +float checkrules_firstblood; + +.float damagedbycontents; +.float damagedbytriggers; + +float yoda; +float damage_goodhits; +float damage_gooddamage; + +.float pain_finished; // Added by Supajoe + +.float dmg_team; +.float teamkill_complain; +.float teamkill_soundtime; +.entity teamkill_soundsource; +.entity pusher; +.bool istypefrag; +.float taunt_soundtime; + +.float spawnshieldtime; + +.int totalfrags; + +.bool canteamdamage; + +.vector death_origin; + +.float damage_dealt, typehitsound, killsound; + +// used for custom deathtype +string deathmessage; + +float IsFlying(entity a); + +void UpdateFrags(entity player, int f); + +// NOTE: f=0 means still count as a (positive) kill, but count no frags for it +void W_SwitchWeapon_Force(Player this, Weapon w, .entity weaponentity); +void GiveFrags (entity attacker, entity targ, float f, int deathtype, .entity weaponentity); + +string AppendItemcodes(string s, entity player); + +void LogDeath(string mode, int deathtype, entity killer, entity killed); + +void Obituary_SpecialDeath( + entity notif_target, + float murder, + int deathtype, + string s1, string s2, string s3, + float f1, float f2, float f3); + +float w_deathtype; +float Obituary_WeaponDeath( + entity notif_target, + float murder, + int deathtype, + string s1, string s2, string s3, + float f1, float f2); + +void Obituary(entity attacker, entity inflictor, entity targ, int deathtype, .entity weaponentity); + +// Frozen status effect +//const int FROZEN_NOT = 0; +const int FROZEN_NORMAL = 1; +const int FROZEN_TEMP_REVIVING = 2; +const int FROZEN_TEMP_DYING = 3; + +.float revival_time; // time at which player was last revived +.float revive_speed; // NOTE: multiplier (anything above 1 is instaheal) +.float freeze_time; +.entity iceblock; +.entity frozen_by; // for ice fields + +void Ice_Think(entity this); + +void Freeze(entity targ, float revivespeed, int frozen_type, bool show_waypoint); + +void Unfreeze(entity targ, bool reset_health); + +// WEAPONTODO +#define DMG_NOWEP (weaponentities[0]) + +// NOTE: the .weaponentity parameter can be set to DMG_NOWEP if the attack wasn't caused by a weapon or player +void Damage (entity targ, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force); + +float RadiusDamage_running; +float RadiusDamageForSource (entity inflictor, vector inflictororigin, vector inflictorvelocity, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe, float inflictorselfdamage, float forceintensity, float forcezscale, int deathtype, .entity weaponentity, entity directhitentity); + // Returns total damage applies to creatures + +float RadiusDamage (entity inflictor, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe, float forceintensity, int deathtype, .entity weaponentity, entity directhitentity); + +.float damageforcescale; +const float MIN_DAMAGEEXTRARADIUS = 2; +const float MAX_DAMAGEEXTRARADIUS = 16; +.float damageextraradius; + +// Calls .event_heal on the target so that they can handle healing themselves +// a limit of RES_LIMIT_NONE should be handled by the entity as its max health (if applicable) +bool Heal(entity targ, entity inflictor, float amount, float limit); + +.float fire_damagepersec; +.float fire_endtime; +.float fire_deathtype; +.entity fire_owner; +.float fire_hitsound; +.entity fire_burner; + +void fireburner_think(entity this); + +float Fire_IsBurning(entity e); + +float Fire_AddDamage(entity e, entity o, float d, float t, float dt); + +void Fire_ApplyDamage(entity e); + +void Fire_ApplyEffect(entity e); + +IntrusiveList g_damagedbycontents; +STATIC_INIT(g_damagedbycontents) { g_damagedbycontents = IL_NEW(); } diff --git a/qcsrc/server/g_damage.qc b/qcsrc/server/g_damage.qc deleted file mode 100644 index f5d5f91bc..000000000 --- a/qcsrc/server/g_damage.qc +++ /dev/null @@ -1,1301 +0,0 @@ -#include "g_damage.qh" - -#include -#include "bot/api.qh" -#include "g_hook.qh" -#include -#include -#include -#include -#include -#include "teamplay.qh" -#include "scores.qh" -#include "spawnpoints.qh" -#include "../common/state.qh" -#include "../common/physics/player.qh" -#include "resources.qh" -#include "../common/vehicles/all.qh" -#include "../common/items/_mod.qh" -#include "../common/mutators/mutator/waypoints/waypointsprites.qh" -#include "../common/mutators/mutator/instagib/sv_instagib.qh" -#include "../common/mutators/mutator/buffs/buffs.qh" -#include "weapons/accuracy.qh" -#include "weapons/csqcprojectile.qh" -#include "weapons/selection.qh" -#include "../common/constants.qh" -#include "../common/deathtypes/all.qh" -#include -#include -#include "../common/notifications/all.qh" -#include "../common/physics/movetypes/movetypes.qh" -#include "../common/playerstats.qh" -#include "../common/teams.qh" -#include "../common/util.qh" -#include -#include -#include -#include "../lib/csqcmodel/sv_model.qh" -#include "../lib/warpzone/common.qh" - -void UpdateFrags(entity player, int f) -{ - GameRules_scoring_add_team(player, SCORE, f); -} - -void GiveFrags(entity attacker, entity targ, float f, int deathtype, .entity weaponentity) -{ - // TODO route through PlayerScores instead - if(game_stopped) return; - - if(f < 0) - { - if(targ == attacker) - { - // suicide - GameRules_scoring_add(attacker, SUICIDES, 1); - } - else - { - // teamkill - GameRules_scoring_add(attacker, TEAMKILLS, 1); - } - } - else - { - // regular frag - GameRules_scoring_add(attacker, KILLS, 1); - if(!warmup_stage && targ.playerid) - PlayerStats_GameReport_Event_Player(attacker, sprintf("kills-%d", targ.playerid), 1); - } - - GameRules_scoring_add(targ, DEATHS, 1); - - // FIXME fix the mess this is (we have REAL points now!) - if(MUTATOR_CALLHOOK(GiveFragsForKill, attacker, targ, f, deathtype, attacker.(weaponentity))) - f = M_ARGV(2, float); - - attacker.totalfrags += f; - - if(f) - UpdateFrags(attacker, f); -} - -string AppendItemcodes(string s, entity player) -{ - for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) - { - .entity weaponentity = weaponentities[slot]; - int w = player.(weaponentity).m_weapon.m_id; - if(w == 0) - w = player.(weaponentity).cnt; // previous weapon - if(w != 0 || slot == 0) - s = strcat(s, ftos(w)); - } - if(time < STAT(STRENGTH_FINISHED, player)) - s = strcat(s, "S"); - if(time < STAT(INVINCIBLE_FINISHED, player)) - s = strcat(s, "I"); - if(PHYS_INPUT_BUTTON_CHAT(player)) - s = strcat(s, "T"); - // TODO: include these codes as a flag on the item itself - MUTATOR_CALLHOOK(LogDeath_AppendItemCodes, player, s); - s = M_ARGV(1, string); - return s; -} - -void LogDeath(string mode, int deathtype, entity killer, entity killed) -{ - string s; - if(!autocvar_sv_eventlog) - return; - s = strcat(":kill:", mode); - s = strcat(s, ":", ftos(killer.playerid)); - s = strcat(s, ":", ftos(killed.playerid)); - s = strcat(s, ":type=", Deathtype_Name(deathtype)); - s = strcat(s, ":items="); - s = AppendItemcodes(s, killer); - if(killed != killer) - { - s = strcat(s, ":victimitems="); - s = AppendItemcodes(s, killed); - } - GameLogEcho(s); -} - -void Obituary_SpecialDeath( - entity notif_target, - float murder, - int deathtype, - string s1, string s2, string s3, - float f1, float f2, float f3) -{ - if(!DEATH_ISSPECIAL(deathtype)) - { - backtrace("Obituary_SpecialDeath called without a special deathtype?\n"); - return; - } - - entity deathent = REGISTRY_GET(Deathtypes, deathtype - DT_FIRST); - if (!deathent) - { - backtrace("Obituary_SpecialDeath: Could not find deathtype entity!\n"); - return; - } - - if(g_cts && deathtype == DEATH_KILL.m_id) - return; // TODO: somehow put this in CTS gamemode file! - - Notification death_message = (murder) ? deathent.death_msgmurder : deathent.death_msgself; - if(death_message) - { - Send_Notification_WOCOVA( - NOTIF_ONE, - notif_target, - MSG_MULTI, - death_message, - s1, s2, s3, "", - f1, f2, f3, 0 - ); - Send_Notification_WOCOVA( - NOTIF_ALL_EXCEPT, - notif_target, - MSG_INFO, - death_message.nent_msginfo, - s1, s2, s3, "", - f1, f2, f3, 0 - ); - } -} - -float Obituary_WeaponDeath( - entity notif_target, - float murder, - int deathtype, - string s1, string s2, string s3, - float f1, float f2) -{ - Weapon death_weapon = DEATH_WEAPONOF(deathtype); - if (death_weapon == WEP_Null) - return false; - - w_deathtype = deathtype; - Notification death_message = ((murder) ? death_weapon.wr_killmessage(death_weapon) : death_weapon.wr_suicidemessage(death_weapon)); - w_deathtype = false; - - if (death_message) - { - Send_Notification_WOCOVA( - NOTIF_ONE, - notif_target, - MSG_MULTI, - death_message, - s1, s2, s3, "", - f1, f2, 0, 0 - ); - // send the info part to everyone - Send_Notification_WOCOVA( - NOTIF_ALL_EXCEPT, - notif_target, - MSG_INFO, - death_message.nent_msginfo, - s1, s2, s3, "", - f1, f2, 0, 0 - ); - } - else - { - LOG_TRACEF( - "Obituary_WeaponDeath(): ^1Deathtype ^7(%d)^1 has no notification for weapon %s!\n", - deathtype, - death_weapon.netname - ); - } - - return true; -} - -bool frag_centermessage_override(entity attacker, entity targ, int deathtype, int kill_count_to_attacker, int kill_count_to_target, string attacker_name) -{ - if(deathtype == DEATH_FIRE.m_id) - { - Send_Notification(NOTIF_ONE, attacker, MSG_CHOICE, CHOICE_FRAG_FIRE, targ.netname, kill_count_to_attacker, (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping)); - Send_Notification(NOTIF_ONE, targ, MSG_CHOICE, CHOICE_FRAGGED_FIRE, attacker_name, kill_count_to_target, GetResource(attacker, RES_HEALTH), GetResource(attacker, RES_ARMOR), (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping)); - return true; - } - - return MUTATOR_CALLHOOK(FragCenterMessage, attacker, targ, deathtype, kill_count_to_attacker, kill_count_to_target); -} - -void Obituary(entity attacker, entity inflictor, entity targ, int deathtype, .entity weaponentity) -{ - // Sanity check - if (!IS_PLAYER(targ)) { backtrace("Obituary called on non-player?!\n"); return; } - - // Declarations - float notif_firstblood = false; - float kill_count_to_attacker, kill_count_to_target; - bool notif_anonymous = false; - string attacker_name = attacker.netname; - - // Set final information for the death - targ.death_origin = targ.origin; - string deathlocation = (autocvar_notification_server_allows_location ? NearestLocation(targ.death_origin) : ""); - - // Abort now if a mutator requests it - if (MUTATOR_CALLHOOK(ClientObituary, inflictor, attacker, targ, deathtype, attacker.(weaponentity))) { CS(targ).killcount = 0; return; } - notif_anonymous = M_ARGV(5, bool); - - if(notif_anonymous) - attacker_name = "Anonymous player"; - - #ifdef NOTIFICATIONS_DEBUG - Debug_Notification( - sprintf( - "Obituary(%s, %s, %s, %s = %d);\n", - attacker_name, - inflictor.netname, - targ.netname, - Deathtype_Name(deathtype), - deathtype - ) - ); - #endif - - // ======= - // SUICIDE - // ======= - if(targ == attacker) - { - if(DEATH_ISSPECIAL(deathtype)) - { - if(deathtype == DEATH_TEAMCHANGE.m_id || deathtype == DEATH_AUTOTEAMCHANGE.m_id) - { - Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", targ.team, 0, 0); - } - else - { - switch(DEATH_ENT(deathtype)) - { - case DEATH_MIRRORDAMAGE: - { - Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0, 0); - break; - } - - default: - { - Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0, 0); - break; - } - } - } - } - else if (!Obituary_WeaponDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0)) - { - backtrace("SUICIDE: what the hell happened here?\n"); - return; - } - LogDeath("suicide", deathtype, targ, targ); - if(deathtype != DEATH_AUTOTEAMCHANGE.m_id) // special case: don't negate frags if auto switched - GiveFrags(attacker, targ, -1, deathtype, weaponentity); - } - - // ====== - // MURDER - // ====== - else if(IS_PLAYER(attacker)) - { - if(SAME_TEAM(attacker, targ)) - { - LogDeath("tk", deathtype, attacker, targ); - GiveFrags(attacker, targ, -1, deathtype, weaponentity); - - CS(attacker).killcount = 0; - - Send_Notification(NOTIF_ONE, attacker, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAG, targ.netname); - Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAGGED, attacker_name); - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(targ.team, INFO_DEATH_TEAMKILL), targ.netname, attacker_name, deathlocation, CS(targ).killcount); - - // In this case, the death message will ALWAYS be "foo was betrayed by bar" - // No need for specific death/weapon messages... - } - else - { - LogDeath("frag", deathtype, attacker, targ); - GiveFrags(attacker, targ, 1, deathtype, weaponentity); - - CS(attacker).taunt_soundtime = time + 1; - CS(attacker).killcount = CS(attacker).killcount + 1; - - attacker.killsound += 1; - - // TODO: improve SPREE_ITEM and KILL_SPREE_LIST - // these 2 macros are spread over multiple files - #define SPREE_ITEM(counta,countb,center,normal,gentle) \ - case counta: \ - Send_Notification(NOTIF_ONE, attacker, MSG_ANNCE, ANNCE_KILLSTREAK_##countb); \ - if (!warmup_stage) \ - PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_##counta, 1); \ - break; - - switch(CS(attacker).killcount) - { - KILL_SPREE_LIST - default: break; - } - #undef SPREE_ITEM - - if(!warmup_stage && !checkrules_firstblood) - { - checkrules_firstblood = true; - notif_firstblood = true; // modify the current messages so that they too show firstblood information - PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_FIRSTBLOOD, 1); - PlayerStats_GameReport_Event_Player(targ, PLAYERSTATS_ACHIEVEMENT_FIRSTVICTIM, 1); - - // tell spree_inf and spree_cen that this is a first-blood and first-victim event - kill_count_to_attacker = -1; - kill_count_to_target = -2; - } - else - { - kill_count_to_attacker = CS(attacker).killcount; - kill_count_to_target = 0; - } - - if(targ.istypefrag) - { - Send_Notification( - NOTIF_ONE, - attacker, - MSG_CHOICE, - CHOICE_TYPEFRAG, - targ.netname, - kill_count_to_attacker, - (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping) - ); - Send_Notification( - NOTIF_ONE, - targ, - MSG_CHOICE, - CHOICE_TYPEFRAGGED, - attacker_name, - kill_count_to_target, - GetResource(attacker, RES_HEALTH), - GetResource(attacker, RES_ARMOR), - (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping) - ); - } - else if(!frag_centermessage_override(attacker, targ, deathtype, kill_count_to_attacker, kill_count_to_target, attacker_name)) - { - Send_Notification( - NOTIF_ONE, - attacker, - MSG_CHOICE, - CHOICE_FRAG, - targ.netname, - kill_count_to_attacker, - (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping) - ); - Send_Notification( - NOTIF_ONE, - targ, - MSG_CHOICE, - CHOICE_FRAGGED, - attacker_name, - kill_count_to_target, - GetResource(attacker, RES_HEALTH), - GetResource(attacker, RES_ARMOR), - (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping) - ); - } - - int f3 = 0; - if(deathtype == DEATH_BUFF.m_id) - f3 = buff_FirstFromFlags(STAT(BUFFS, attacker)).m_id; - - if (!Obituary_WeaponDeath(targ, true, deathtype, targ.netname, attacker_name, deathlocation, CS(targ).killcount, kill_count_to_attacker)) - Obituary_SpecialDeath(targ, true, deathtype, targ.netname, attacker_name, deathlocation, CS(targ).killcount, kill_count_to_attacker, f3); - } - } - - // ============= - // ACCIDENT/TRAP - // ============= - else - { - switch(DEATH_ENT(deathtype)) - { - // For now, we're just forcing HURTTRIGGER to behave as "DEATH_VOID" and giving it no special options... - // Later on you will only be able to make custom messages using DEATH_CUSTOM, - // and there will be a REAL DEATH_VOID implementation which mappers will use. - case DEATH_HURTTRIGGER: - { - Obituary_SpecialDeath(targ, false, deathtype, - targ.netname, - inflictor.message, - deathlocation, - CS(targ).killcount, - 0, - 0); - break; - } - - case DEATH_CUSTOM: - { - Obituary_SpecialDeath(targ, false, deathtype, - targ.netname, - ((strstrofs(deathmessage, "%", 0) < 0) ? strcat("%s ", deathmessage) : deathmessage), - deathlocation, - CS(targ).killcount, - 0, - 0); - break; - } - - default: - { - Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0, 0); - break; - } - } - - LogDeath("accident", deathtype, targ, targ); - GiveFrags(targ, targ, -1, deathtype, weaponentity); - - if(GameRules_scoring_add(targ, SCORE, 0) == -5) - { - Send_Notification(NOTIF_ONE, targ, MSG_ANNCE, ANNCE_ACHIEVEMENT_BOTLIKE); - if (!warmup_stage) - { - PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_BOTLIKE, 1); - } - } - } - - // reset target kill count - CS(targ).killcount = 0; -} - -void Ice_Think(entity this) -{ - if(!STAT(FROZEN, this.owner) || this.owner.iceblock != this) - { - delete(this); - return; - } - vector ice_org = this.owner.origin - '0 0 16'; - if (this.origin != ice_org) - setorigin(this, ice_org); - this.nextthink = time; -} - -void Freeze(entity targ, float revivespeed, int frozen_type, bool show_waypoint) -{ - if(!IS_PLAYER(targ) && !IS_MONSTER(targ)) // TODO: only specified entities can be freezed - return; - - if(STAT(FROZEN, targ)) - return; - - float targ_maxhealth = ((IS_MONSTER(targ)) ? targ.max_health : start_health); - - STAT(FROZEN, targ) = frozen_type; - STAT(REVIVE_PROGRESS, targ) = ((frozen_type == FROZEN_TEMP_DYING) ? 1 : 0); - SetResource(targ, RES_HEALTH, ((frozen_type == FROZEN_TEMP_DYING) ? targ_maxhealth : 1)); - targ.revive_speed = revivespeed; - if(targ.bot_attack) - IL_REMOVE(g_bot_targets, targ); - targ.bot_attack = false; - targ.freeze_time = time; - - entity ice = new(ice); - ice.owner = targ; - ice.scale = targ.scale; - // set_movetype(ice, MOVETYPE_FOLLOW) would rotate the ice model with the player - setthink(ice, Ice_Think); - ice.nextthink = time; - ice.frame = floor(random() * 21); // ice model has 20 different looking frames - setmodel(ice, MDL_ICE); - ice.alpha = 1; - ice.colormod = Team_ColorRGB(targ.team); - ice.glowmod = ice.colormod; - targ.iceblock = ice; - targ.revival_time = 0; - - Ice_Think(ice); - - RemoveGrapplingHooks(targ); - - FOREACH_CLIENT(IS_PLAYER(it), - { - for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) - { - .entity weaponentity = weaponentities[slot]; - if(it.(weaponentity).hook.aiment == targ) - RemoveHook(it.(weaponentity).hook); - } - }); - - // add waypoint - if(MUTATOR_CALLHOOK(Freeze, targ, revivespeed, frozen_type) || show_waypoint) - WaypointSprite_Spawn(WP_Frozen, 0, 0, targ, '0 0 64', NULL, targ.team, targ, waypointsprite_attached, true, RADARICON_WAYPOINT); -} - -void Unfreeze(entity targ, bool reset_health) -{ - if(!STAT(FROZEN, targ)) - return; - - if (reset_health && STAT(FROZEN, targ) != FROZEN_TEMP_DYING) - SetResource(targ, RES_HEALTH, ((IS_PLAYER(targ)) ? start_health : targ.max_health)); - - targ.pauseregen_finished = time + autocvar_g_balance_pause_health_regen; - - STAT(FROZEN, targ) = 0; - STAT(REVIVE_PROGRESS, targ) = 0; - targ.revival_time = time; - if(!targ.bot_attack) - IL_PUSH(g_bot_targets, targ); - targ.bot_attack = true; - - WaypointSprite_Kill(targ.waypointsprite_attached); - - FOREACH_CLIENT(IS_PLAYER(it), - { - for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) - { - .entity weaponentity = weaponentities[slot]; - if(it.(weaponentity).hook.aiment == targ) - RemoveHook(it.(weaponentity).hook); - } - }); - - // remove the ice block - if(targ.iceblock) - delete(targ.iceblock); - targ.iceblock = NULL; - - MUTATOR_CALLHOOK(Unfreeze, targ); -} - -void Damage(entity targ, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force) -{ - float complainteamdamage = 0; - float mirrordamage = 0; - float mirrorforce = 0; - - if (game_stopped || (IS_CLIENT(targ) && CS(targ).killcount == FRAGS_SPECTATOR)) - return; - - entity attacker_save = attacker; - - // special rule: gravity bombs and sound-based attacks do not affect team mates (other than for disconnecting the hook) - if(DEATH_ISWEAPON(deathtype, WEP_HOOK) || (deathtype & HITTYPE_SOUND)) - { - if(IS_PLAYER(targ) && SAME_TEAM(targ, attacker)) - { - return; - } - } - - if(deathtype == DEATH_KILL.m_id || deathtype == DEATH_TEAMCHANGE.m_id || deathtype == DEATH_AUTOTEAMCHANGE.m_id) - { - // exit the vehicle before killing (fixes a crash) - if(IS_PLAYER(targ) && targ.vehicle) - vehicles_exit(targ.vehicle, VHEF_RELEASE); - - // These are ALWAYS lethal - // No damage modification here - // Instead, prepare the victim for his death... - SetResourceExplicit(targ, RES_ARMOR, 0); - targ.spawnshieldtime = 0; - SetResourceExplicit(targ, RES_HEALTH, 0.9); // this is < 1 - targ.flags -= targ.flags & FL_GODMODE; - damage = 100000; - } - else if(deathtype == DEATH_MIRRORDAMAGE.m_id || deathtype == DEATH_NOAMMO.m_id) - { - // no processing - } - else - { - // nullify damage if teamplay is on - if(deathtype != DEATH_TELEFRAG.m_id) - if(IS_PLAYER(attacker)) - { - if(IS_PLAYER(targ) && targ != attacker && (IS_INDEPENDENT_PLAYER(attacker) || IS_INDEPENDENT_PLAYER(targ))) - { - damage = 0; - force = '0 0 0'; - } - else if(SAME_TEAM(attacker, targ)) - { - if(autocvar_teamplay_mode == 1) - damage = 0; - else if(attacker != targ) - { - if(autocvar_teamplay_mode == 2) - { - if(IS_PLAYER(targ) && !IS_DEAD(targ)) - { - attacker.dmg_team = attacker.dmg_team + damage; - complainteamdamage = attacker.dmg_team - autocvar_g_teamdamage_threshold; - } - } - else if(autocvar_teamplay_mode == 3) - damage = 0; - else if(autocvar_teamplay_mode == 4) - { - if(IS_PLAYER(targ) && !IS_DEAD(targ)) - { - attacker.dmg_team = attacker.dmg_team + damage; - complainteamdamage = attacker.dmg_team - autocvar_g_teamdamage_threshold; - if(complainteamdamage > 0) - mirrordamage = autocvar_g_mirrordamage * complainteamdamage; - mirrorforce = autocvar_g_mirrordamage * vlen(force); - damage = autocvar_g_friendlyfire * damage; - // mirrordamage will be used LATER - - if(autocvar_g_mirrordamage_virtual) - { - vector v = healtharmor_applydamage(GetResource(attacker, RES_ARMOR), autocvar_g_balance_armor_blockpercent, deathtype, mirrordamage); - attacker.dmg_take += v.x; - attacker.dmg_save += v.y; - attacker.dmg_inflictor = inflictor; - mirrordamage = v.z; - mirrorforce = 0; - } - - if(autocvar_g_friendlyfire_virtual) - { - vector v = healtharmor_applydamage(GetResource(targ, RES_ARMOR), autocvar_g_balance_armor_blockpercent, deathtype, damage); - targ.dmg_take += v.x; - targ.dmg_save += v.y; - targ.dmg_inflictor = inflictor; - damage = 0; - if(!autocvar_g_friendlyfire_virtual_force) - force = '0 0 0'; - } - } - else if(!targ.canteamdamage) - damage = 0; - } - } - } - } - - if (!DEATH_ISSPECIAL(deathtype)) - { - damage *= g_weapondamagefactor; - mirrordamage *= g_weapondamagefactor; - complainteamdamage *= g_weapondamagefactor; - force = force * g_weaponforcefactor; - mirrorforce *= g_weaponforcefactor; - } - - // should this be changed at all? If so, in what way? - MUTATOR_CALLHOOK(Damage_Calculate, inflictor, attacker, targ, deathtype, damage, mirrordamage, force, attacker.(weaponentity)); - damage = M_ARGV(4, float); - mirrordamage = M_ARGV(5, float); - force = M_ARGV(6, vector); - - if(IS_PLAYER(targ) && damage > 0 && attacker) - { - for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) - { - .entity went = weaponentities[slot]; - if(targ.(went).hook && targ.(went).hook.aiment == attacker) - RemoveHook(targ.(went).hook); - } - } - - if(STAT(FROZEN, targ) && !ITEM_DAMAGE_NEEDKILL(deathtype) - && deathtype != DEATH_TEAMCHANGE.m_id && deathtype != DEATH_AUTOTEAMCHANGE.m_id) - { - if(autocvar_g_frozen_revive_falldamage > 0 && deathtype == DEATH_FALL.m_id && damage >= autocvar_g_frozen_revive_falldamage) - { - Unfreeze(targ, false); - SetResource(targ, RES_HEALTH, autocvar_g_frozen_revive_falldamage_health); - Send_Effect(EFFECT_ICEORGLASS, targ.origin, '0 0 0', 3); - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_REVIVED_FALL, targ.netname); - Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_FREEZETAG_REVIVE_SELF); - } - - damage = 0; - force *= autocvar_g_frozen_force; - } - - if(IS_PLAYER(targ) && STAT(FROZEN, targ) - && ITEM_DAMAGE_NEEDKILL(deathtype) && !autocvar_g_frozen_damage_trigger) - { - Send_Effect(EFFECT_TELEPORT, targ.origin, '0 0 0', 1); - - entity spot = SelectSpawnPoint(targ, false); - if(spot) - { - damage = 0; - targ.deadflag = DEAD_NO; - - targ.angles = spot.angles; - - targ.effects = 0; - targ.effects |= EF_TELEPORT_BIT; - - targ.angles_z = 0; // never spawn tilted even if the spot says to - targ.fixangle = true; // turn this way immediately - targ.velocity = '0 0 0'; - targ.avelocity = '0 0 0'; - targ.punchangle = '0 0 0'; - targ.punchvector = '0 0 0'; - targ.oldvelocity = targ.velocity; - - targ.spawnorigin = spot.origin; - setorigin(targ, spot.origin + '0 0 1' * (1 - targ.mins.z - 24)); - // don't reset back to last position, even if new position is stuck in solid - targ.oldorigin = targ.origin; - - Send_Effect(EFFECT_TELEPORT, targ.origin, '0 0 0', 1); - } - } - - if(!MUTATOR_IS_ENABLED(mutator_instagib)) - { - // apply strength multiplier - if (attacker.items & ITEM_Strength.m_itemid) - { - if(targ == attacker) - { - damage = damage * autocvar_g_balance_powerup_strength_selfdamage; - force = force * autocvar_g_balance_powerup_strength_selfforce; - } - else - { - damage = damage * autocvar_g_balance_powerup_strength_damage; - force = force * autocvar_g_balance_powerup_strength_force; - } - } - - // apply invincibility multiplier - if (targ.items & ITEM_Shield.m_itemid) - { - damage = damage * autocvar_g_balance_powerup_invincible_takedamage; - if (targ != attacker) - { - force = force * autocvar_g_balance_powerup_invincible_takeforce; - } - } - } - - if (targ == attacker) - damage = damage * autocvar_g_balance_selfdamagepercent; // Partial damage if the attacker hits himself - - // count the damage - if(attacker) - if(!IS_DEAD(targ)) - if(deathtype != DEATH_BUFF.m_id) - if(targ.takedamage == DAMAGE_AIM) - if(targ != attacker) - { - entity victim; - if(IS_VEHICLE(targ) && targ.owner) - victim = targ.owner; - else - victim = targ; - - if(IS_PLAYER(victim) || (IS_TURRET(victim) && victim.active == ACTIVE_ACTIVE) || IS_MONSTER(victim) || MUTATOR_CALLHOOK(PlayHitsound, victim, attacker)) - { - if(DIFF_TEAM(victim, attacker) && !STAT(FROZEN, victim)) - { - if(damage > 0) - { - if(deathtype != DEATH_FIRE.m_id) - { - if(PHYS_INPUT_BUTTON_CHAT(victim)) - attacker.typehitsound += 1; - else - attacker.damage_dealt += damage; - } - - damage_goodhits += 1; - damage_gooddamage += damage; - - if (!DEATH_ISSPECIAL(deathtype)) - { - if(IS_PLAYER(targ)) // don't do this for vehicles - if(IsFlying(victim)) - yoda = 1; - } - } - } - else if(IS_PLAYER(attacker)) - { - // if enemy gets frozen in this frame and receives other damage don't - // play the typehitsound e.g. when hit by multiple bullets of the shotgun - if (deathtype != DEATH_FIRE.m_id && (!STAT(FROZEN, victim) || time > victim.freeze_time)) - { - attacker.typehitsound += 1; - } - if(complainteamdamage > 0) - if(time > CS(attacker).teamkill_complain) - { - CS(attacker).teamkill_complain = time + 5; - CS(attacker).teamkill_soundtime = time + 0.4; - CS(attacker).teamkill_soundsource = targ; - } - } - } - } - } - - // apply push - if (targ.damageforcescale) - if (force) - if (!IS_PLAYER(targ) || time >= targ.spawnshieldtime || targ == attacker) - { - vector farce = damage_explosion_calcpush(targ.damageforcescale * force, targ.velocity, autocvar_g_balance_damagepush_speedfactor); - if(targ.move_movetype == MOVETYPE_PHYSICS) - { - entity farcent = new(farce); - farcent.enemy = targ; - farcent.movedir = farce * 10; - if(targ.mass) - farcent.movedir = farcent.movedir * targ.mass; - farcent.origin = hitloc; - farcent.forcetype = FORCETYPE_FORCEATPOS; - farcent.nextthink = time + 0.1; - setthink(farcent, SUB_Remove); - } - else if(targ.move_movetype != MOVETYPE_NOCLIP) - { - targ.velocity = targ.velocity + farce; - } - UNSET_ONGROUND(targ); - UpdateCSQCProjectile(targ); - } - // apply damage - if (damage != 0 || (targ.damageforcescale && force)) - if (targ.event_damage) - targ.event_damage (targ, inflictor, attacker, damage, deathtype, weaponentity, hitloc, force); - - // apply mirror damage if any - if(!autocvar_g_mirrordamage_onlyweapons || DEATH_WEAPONOF(deathtype) != WEP_Null) - if(mirrordamage > 0 || mirrorforce > 0) - { - attacker = attacker_save; - - force = normalize(attacker.origin + attacker.view_ofs - hitloc) * mirrorforce; - Damage(attacker, inflictor, attacker, mirrordamage, DEATH_MIRRORDAMAGE.m_id, weaponentity, attacker.origin, force); - } -} - -float RadiusDamageForSource (entity inflictor, vector inflictororigin, vector inflictorvelocity, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe, - float inflictorselfdamage, float forceintensity, float forcezscale, int deathtype, .entity weaponentity, entity directhitentity) - // Returns total damage applies to creatures -{ - entity targ; - vector force; - float total_damage_to_creatures; - entity next; - float tfloordmg; - float tfloorforce; - - float stat_damagedone; - - if(RadiusDamage_running) - { - backtrace("RadiusDamage called recursively! Expect stuff to go HORRIBLY wrong."); - return 0; - } - - RadiusDamage_running = 1; - - tfloordmg = autocvar_g_throughfloor_damage; - tfloorforce = autocvar_g_throughfloor_force; - - total_damage_to_creatures = 0; - - if(deathtype != (WEP_HOOK.m_id | HITTYPE_SECONDARY | HITTYPE_BOUNCE)) // only send gravity bomb damage once - if(!(deathtype & HITTYPE_SOUND)) // do not send radial sound damage (bandwidth hog) - { - force = inflictorvelocity; - if(force == '0 0 0') - force = '0 0 -1'; - else - force = normalize(force); - if(forceintensity >= 0) - Damage_DamageInfo(inflictororigin, coredamage, edgedamage, rad, forceintensity * force, deathtype, 0, attacker); - else - Damage_DamageInfo(inflictororigin, coredamage, edgedamage, -rad, (-forceintensity) * force, deathtype, 0, attacker); - } - - stat_damagedone = 0; - - targ = WarpZone_FindRadius (inflictororigin, rad + MAX_DAMAGEEXTRARADIUS, false); - while (targ) - { - next = targ.chain; - if ((targ != inflictor) || inflictorselfdamage) - if (((cantbe != targ) && !mustbe) || (mustbe == targ)) - if (targ.takedamage) - { - vector nearest; - vector diff; - float power; - - // LordHavoc: measure distance to nearest point on target (not origin) - // (this guarentees 100% damage on a touch impact) - nearest = targ.WarpZone_findradius_nearest; - diff = targ.WarpZone_findradius_dist; - // round up a little on the damage to ensure full damage on impacts - // and turn the distance into a fraction of the radius - power = 1 - ((vlen (diff) - bound(MIN_DAMAGEEXTRARADIUS, targ.damageextraradius, MAX_DAMAGEEXTRARADIUS)) / rad); - //bprint(" "); - //bprint(ftos(power)); - //if (targ == attacker) - // print(ftos(power), "\n"); - if (power > 0) - { - float finaldmg; - if (power > 1) - power = 1; - finaldmg = coredamage * power + edgedamage * (1 - power); - if (finaldmg > 0) - { - float a; - float c; - vector hitloc; - vector myblastorigin; - vector center; - - myblastorigin = WarpZone_TransformOrigin(targ, inflictororigin); - - // if it's a player, use the view origin as reference - center = CENTER_OR_VIEWOFS(targ); - - force = normalize(center - myblastorigin); - force = force * (finaldmg / coredamage) * forceintensity; - hitloc = nearest; - - // apply special scaling along the z axis if set - // NOTE: 0 value is not allowed for compatibility, in the case of weapon cvars not being set - if(forcezscale) - force.z *= forcezscale; - - if(targ != directhitentity) - { - float hits; - float total; - float hitratio; - float mininv_f, mininv_d; - - // test line of sight to multiple positions on box, - // and do damage if any of them hit - hits = 0; - - // we know: max stddev of hitratio = 1 / (2 * sqrt(n)) - // so for a given max stddev: - // n = (1 / (2 * max stddev of hitratio))^2 - - mininv_d = (finaldmg * (1-tfloordmg)) / autocvar_g_throughfloor_damage_max_stddev; - mininv_f = (vlen(force) * (1-tfloorforce)) / autocvar_g_throughfloor_force_max_stddev; - - if(autocvar_g_throughfloor_debug) - LOG_INFOF("THROUGHFLOOR: D=%f F=%f max(dD)=1/%f max(dF)=1/%f", finaldmg, vlen(force), mininv_d, mininv_f); - - - total = 0.25 * (max(mininv_f, mininv_d) ** 2); - - if(autocvar_g_throughfloor_debug) - LOG_INFOF(" steps=%f", total); - - - if (IS_PLAYER(targ)) - total = ceil(bound(autocvar_g_throughfloor_min_steps_player, total, autocvar_g_throughfloor_max_steps_player)); - else - total = ceil(bound(autocvar_g_throughfloor_min_steps_other, total, autocvar_g_throughfloor_max_steps_other)); - - if(autocvar_g_throughfloor_debug) - LOG_INFOF(" steps=%f dD=%f dF=%f", total, finaldmg * (1-tfloordmg) / (2 * sqrt(total)), vlen(force) * (1-tfloorforce) / (2 * sqrt(total))); - - for(c = 0; c < total; ++c) - { - //traceline(targ.WarpZone_findradius_findorigin, nearest, MOVE_NOMONSTERS, inflictor); - WarpZone_TraceLine(inflictororigin, WarpZone_UnTransformOrigin(targ, nearest), MOVE_NOMONSTERS, inflictor); - if (trace_fraction == 1 || trace_ent == targ) - { - ++hits; - if (hits > 1) - hitloc = hitloc + nearest; - else - hitloc = nearest; - } - nearest.x = targ.origin.x + targ.mins.x + random() * targ.size.x; - nearest.y = targ.origin.y + targ.mins.y + random() * targ.size.y; - nearest.z = targ.origin.z + targ.mins.z + random() * targ.size.z; - } - - nearest = hitloc * (1 / max(1, hits)); - hitratio = (hits / total); - a = bound(0, tfloordmg + (1-tfloordmg) * hitratio, 1); - finaldmg = finaldmg * a; - a = bound(0, tfloorforce + (1-tfloorforce) * hitratio, 1); - force = force * a; - - if(autocvar_g_throughfloor_debug) - LOG_INFOF(" D=%f F=%f", finaldmg, vlen(force)); - } - - //if (targ == attacker) - //{ - // print("hits ", ftos(hits), " / ", ftos(total)); - // print(" finaldmg ", ftos(finaldmg), " force ", vtos(force)); - // print(" (", ftos(a), ")\n"); - //} - if(finaldmg || force) - { - if(targ.iscreature) - { - total_damage_to_creatures += finaldmg; - - if(accuracy_isgooddamage(attacker, targ)) - stat_damagedone += finaldmg; - } - - if(targ == directhitentity || DEATH_ISSPECIAL(deathtype)) - Damage(targ, inflictor, attacker, finaldmg, deathtype, weaponentity, nearest, force); - else - Damage(targ, inflictor, attacker, finaldmg, deathtype | HITTYPE_SPLASH, weaponentity, nearest, force); - } - } - } - } - targ = next; - } - - RadiusDamage_running = 0; - - if(!DEATH_ISSPECIAL(deathtype)) - accuracy_add(attacker, DEATH_WEAPONOF(deathtype), 0, min(coredamage, stat_damagedone)); - - return total_damage_to_creatures; -} - -float RadiusDamage(entity inflictor, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe, float forceintensity, int deathtype, .entity weaponentity, entity directhitentity) -{ - return RadiusDamageForSource(inflictor, (inflictor.origin + (inflictor.mins + inflictor.maxs) * 0.5), inflictor.velocity, attacker, coredamage, edgedamage, rad, - cantbe, mustbe, false, forceintensity, 1, deathtype, weaponentity, directhitentity); -} - -bool Heal(entity targ, entity inflictor, float amount, float limit) -{ - if(game_stopped || (IS_CLIENT(targ) && CS(targ).killcount == FRAGS_SPECTATOR) || STAT(FROZEN, targ) || IS_DEAD(targ)) - return false; - - bool healed = false; - if(targ.event_heal) - healed = targ.event_heal(targ, inflictor, amount, limit); - // TODO: additional handling? what if the healing kills them? should this abort if healing would do so etc - // TODO: healing fx! - // TODO: armor healing? - return healed; -} - -float Fire_IsBurning(entity e) -{ - return (time < e.fire_endtime); -} - -float Fire_AddDamage(entity e, entity o, float d, float t, float dt) -{ - float dps; - float maxtime, mintime, maxdamage, mindamage, maxdps, mindps, totaldamage, totaltime; - - if(IS_PLAYER(e)) - { - if(IS_DEAD(e)) - return -1; - } - else - { - if(!e.fire_burner) - { - // print("adding a fire burner to ", e.classname, "\n"); - e.fire_burner = new(fireburner); - setthink(e.fire_burner, fireburner_think); - e.fire_burner.nextthink = time; - e.fire_burner.owner = e; - } - } - - t = max(t, 0.1); - dps = d / t; - if(Fire_IsBurning(e)) - { - mintime = e.fire_endtime - time; - maxtime = max(mintime, t); - - mindps = e.fire_damagepersec; - maxdps = max(mindps, dps); - - if(maxtime > mintime || maxdps > mindps) - { - // Constraints: - - // damage we have right now - mindamage = mindps * mintime; - - // damage we want to get - maxdamage = mindamage + d; - - // but we can't exceed maxtime * maxdps! - totaldamage = min(maxdamage, maxtime * maxdps); - - // LEMMA: - // Look at: - // totaldamage = min(mindamage + d, maxtime * maxdps) - // We see: - // totaldamage <= maxtime * maxdps - // ==> totaldamage / maxdps <= maxtime. - // We also see: - // totaldamage / mindps = min(mindamage / mindps + d, maxtime * maxdps / mindps) - // >= min(mintime, maxtime) - // ==> totaldamage / maxdps >= mintime. - - /* - // how long do we damage then? - // at least as long as before - // but, never exceed maxdps - totaltime = max(mintime, totaldamage / maxdps); // always <= maxtime due to lemma - */ - - // alternate: - // at most as long as maximum allowed - // but, never below mindps - totaltime = min(maxtime, totaldamage / mindps); // always >= mintime due to lemma - - // assuming t > mintime, dps > mindps: - // we get d = t * dps = maxtime * maxdps - // totaldamage = min(maxdamage, maxtime * maxdps) = min(... + d, maxtime * maxdps) = maxtime * maxdps - // totaldamage / maxdps = maxtime - // totaldamage / mindps > totaldamage / maxdps = maxtime - // FROM THIS: - // a) totaltime = max(mintime, maxtime) = maxtime - // b) totaltime = min(maxtime, totaldamage / maxdps) = maxtime - - // assuming t <= mintime: - // we get maxtime = mintime - // a) totaltime = max(mintime, ...) >= mintime, also totaltime <= maxtime by the lemma, therefore totaltime = mintime = maxtime - // b) totaltime = min(maxtime, ...) <= maxtime, also totaltime >= mintime by the lemma, therefore totaltime = mintime = maxtime - - // assuming dps <= mindps: - // we get mindps = maxdps. - // With this, the lemma says that mintime <= totaldamage / mindps = totaldamage / maxdps <= maxtime. - // a) totaltime = max(mintime, totaldamage / maxdps) = totaldamage / maxdps - // b) totaltime = min(maxtime, totaldamage / mindps) = totaldamage / maxdps - - e.fire_damagepersec = totaldamage / totaltime; - e.fire_endtime = time + totaltime; - if(totaldamage > 1.2 * mindamage) - { - e.fire_deathtype = dt; - if(e.fire_owner != o) - { - e.fire_owner = o; - e.fire_hitsound = false; - } - } - if(accuracy_isgooddamage(o, e)) - accuracy_add(o, DEATH_WEAPONOF(dt), 0, max(0, totaldamage - mindamage)); - return max(0, totaldamage - mindamage); // can never be negative, but to make sure - } - else - return 0; - } - else - { - e.fire_damagepersec = dps; - e.fire_endtime = time + t; - e.fire_deathtype = dt; - e.fire_owner = o; - e.fire_hitsound = false; - if(accuracy_isgooddamage(o, e)) - accuracy_add(o, DEATH_WEAPONOF(dt), 0, d); - return d; - } -} - -void Fire_ApplyDamage(entity e) -{ - float t, d, hi, ty; - entity o; - - if (!Fire_IsBurning(e)) - return; - - for(t = 0, o = e.owner; o.owner && t < 16; o = o.owner, ++t); - if(IS_NOT_A_CLIENT(o)) - o = e.fire_owner; - - // water and slime stop fire - if(e.waterlevel) - if(e.watertype != CONTENT_LAVA) - e.fire_endtime = 0; - - // ice stops fire - if(STAT(FROZEN, e)) - e.fire_endtime = 0; - - t = min(frametime, e.fire_endtime - time); - d = e.fire_damagepersec * t; - - hi = e.fire_owner.damage_dealt; - ty = e.fire_owner.typehitsound; - Damage(e, e, e.fire_owner, d, e.fire_deathtype, DMG_NOWEP, e.origin, '0 0 0'); - if(e.fire_hitsound && e.fire_owner) - { - e.fire_owner.damage_dealt = hi; - e.fire_owner.typehitsound = ty; - } - e.fire_hitsound = true; - - if(!IS_INDEPENDENT_PLAYER(e) && !STAT(FROZEN, e)) - { - IL_EACH(g_damagedbycontents, it.damagedbycontents && it != e, - { - if(!IS_DEAD(it) && it.takedamage && !IS_INDEPENDENT_PLAYER(it)) - if(boxesoverlap(e.absmin, e.absmax, it.absmin, it.absmax)) - { - t = autocvar_g_balance_firetransfer_time * (e.fire_endtime - time); - d = autocvar_g_balance_firetransfer_damage * e.fire_damagepersec * t; - Fire_AddDamage(it, o, d, t, DEATH_FIRE.m_id); - } - }); - } -} - -void Fire_ApplyEffect(entity e) -{ - if(Fire_IsBurning(e)) - e.effects |= EF_FLAME; - else - e.effects &= ~EF_FLAME; -} - -void fireburner_think(entity this) -{ - // for players, this is done in the regular loop - if(wasfreed(this.owner)) - { - delete(this); - return; - } - Fire_ApplyEffect(this.owner); - if(!Fire_IsBurning(this.owner)) - { - this.owner.fire_burner = NULL; - delete(this); - return; - } - Fire_ApplyDamage(this.owner); - this.nextthink = time; -} diff --git a/qcsrc/server/g_damage.qh b/qcsrc/server/g_damage.qh deleted file mode 100644 index 2348c7ad6..000000000 --- a/qcsrc/server/g_damage.qh +++ /dev/null @@ -1,163 +0,0 @@ -#pragma once - -#if defined(CSQC) -#elif defined(MENUQC) -#elif defined(SVQC) - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include "weapons/accuracy.qh" - #include "weapons/csqcprojectile.qh" - #include "weapons/selection.qh" - #include "autocvars.qh" - #include "constants.qh" - #include - #include - #include - #include - #include - #include - #include - #include "g_hook.qh" - #include "scores.qh" - #include "spawnpoints.qh" -#endif - -.void(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force) event_damage; - -.bool(entity targ, entity inflictor, float amount, float limit) event_heal; - -.float dmg; -.float dmg_edge; -.float dmg_force; -.float dmg_radius; - -bool Damage_DamageInfo_SendEntity(entity this, entity to, int sf); - -void Damage_DamageInfo(vector org, float coredamage, float edgedamage, float rad, vector force, int deathtype, float bloodtype, entity dmgowner); - -float checkrules_firstblood; - -.float damagedbycontents; -.float damagedbytriggers; - -float yoda; -float damage_goodhits; -float damage_gooddamage; - -.float pain_finished; // Added by Supajoe - -.float dmg_team; -.float teamkill_complain; -.float teamkill_soundtime; -.entity teamkill_soundsource; -.entity pusher; -.bool istypefrag; -.float taunt_soundtime; - -.float spawnshieldtime; - -.int totalfrags; - -.bool canteamdamage; - -.vector death_origin; - -.float damage_dealt, typehitsound, killsound; - -// used for custom deathtype -string deathmessage; - -float IsFlying(entity a); - -void UpdateFrags(entity player, int f); - -// NOTE: f=0 means still count as a (positive) kill, but count no frags for it -void W_SwitchWeapon_Force(Player this, Weapon w, .entity weaponentity); -void GiveFrags (entity attacker, entity targ, float f, int deathtype, .entity weaponentity); - -string AppendItemcodes(string s, entity player); - -void LogDeath(string mode, int deathtype, entity killer, entity killed); - -void Obituary_SpecialDeath( - entity notif_target, - float murder, - int deathtype, - string s1, string s2, string s3, - float f1, float f2, float f3); - -float w_deathtype; -float Obituary_WeaponDeath( - entity notif_target, - float murder, - int deathtype, - string s1, string s2, string s3, - float f1, float f2); - -void Obituary(entity attacker, entity inflictor, entity targ, int deathtype, .entity weaponentity); - -// Frozen status effect -//const int FROZEN_NOT = 0; -const int FROZEN_NORMAL = 1; -const int FROZEN_TEMP_REVIVING = 2; -const int FROZEN_TEMP_DYING = 3; - -.float revival_time; // time at which player was last revived -.float revive_speed; // NOTE: multiplier (anything above 1 is instaheal) -.float freeze_time; -.entity iceblock; -.entity frozen_by; // for ice fields - -void Ice_Think(entity this); - -void Freeze(entity targ, float revivespeed, int frozen_type, bool show_waypoint); - -void Unfreeze(entity targ, bool reset_health); - -// WEAPONTODO -#define DMG_NOWEP (weaponentities[0]) - -// NOTE: the .weaponentity parameter can be set to DMG_NOWEP if the attack wasn't caused by a weapon or player -void Damage (entity targ, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force); - -float RadiusDamage_running; -float RadiusDamageForSource (entity inflictor, vector inflictororigin, vector inflictorvelocity, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe, float inflictorselfdamage, float forceintensity, float forcezscale, int deathtype, .entity weaponentity, entity directhitentity); - // Returns total damage applies to creatures - -float RadiusDamage (entity inflictor, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe, float forceintensity, int deathtype, .entity weaponentity, entity directhitentity); - -.float damageforcescale; -const float MIN_DAMAGEEXTRARADIUS = 2; -const float MAX_DAMAGEEXTRARADIUS = 16; -.float damageextraradius; - -// Calls .event_heal on the target so that they can handle healing themselves -// a limit of RES_LIMIT_NONE should be handled by the entity as its max health (if applicable) -bool Heal(entity targ, entity inflictor, float amount, float limit); - -.float fire_damagepersec; -.float fire_endtime; -.float fire_deathtype; -.entity fire_owner; -.float fire_hitsound; -.entity fire_burner; - -void fireburner_think(entity this); - -float Fire_IsBurning(entity e); - -float Fire_AddDamage(entity e, entity o, float d, float t, float dt); - -void Fire_ApplyDamage(entity e); - -void Fire_ApplyEffect(entity e); - -IntrusiveList g_damagedbycontents; -STATIC_INIT(g_damagedbycontents) { g_damagedbycontents = IL_NEW(); } diff --git a/qcsrc/server/g_hook.qc b/qcsrc/server/g_hook.qc deleted file mode 100644 index 57947200a..000000000 --- a/qcsrc/server/g_hook.qc +++ /dev/null @@ -1,435 +0,0 @@ -#include "g_hook.qh" - -#include -#include -#include -#include -#include -#include -#include "weapons/common.qh" -#include "weapons/csqcprojectile.qh" -#include "weapons/weaponsystem.qh" -#include "weapons/selection.qh" -#include "weapons/tracing.qh" -#include "player.qh" -#include "command/common.qh" -#include "command/vote.qh" -#include "round_handler.qh" -#include "../common/state.qh" -#include "../common/physics/player.qh" -#include "../common/vehicles/all.qh" -#include "../common/constants.qh" -#include "../common/util.qh" -#include -#include -#include "../lib/warpzone/common.qh" -#include "../lib/warpzone/server.qh" - -/*============================================ - - Wazat's Xonotic Grappling Hook - - Contact: Wazat1@gmail.com - - -Installation instructions: --------------------------- - -1. Place hook.c in your gamec source directory with the other source files. - -2. Add this line to the bottom of progs.src: - -gamec/hook.c - -3. Open defs.h and add these lines to the very bottom: - -// Wazat's grappling hook -.entity hook; -void GrapplingHookFrame(); -void RemoveGrapplingHook(entity pl); -void SetGrappleHookBindings(); -// hook impulses -const float GRAPHOOK_FIRE = 20; -const float GRAPHOOK_RELEASE = 21; -// (note: you can change the hook impulse #'s to whatever you please) - -4. Open client.c and add this to the top of PutClientInServer(): - - RemoveGrapplingHook(this); // Wazat's Grappling Hook - -5. Find ClientConnect() (in client.c) and add these lines to the bottom: - - // Wazat's grappling hook - SetGrappleHookBindings(); - -6. Still in client.c, find PlayerPreThink and add this line just above the call to W_WeaponFrame: - - GrapplingHookFrame(); - -7. Build and test the mod. You'll want to bind a key to "+hook" like this: -bind ctrl "+hook" - -And you should be done! - - -============================================*/ - -void RemoveGrapplingHooks(entity pl) -{ - if(pl.move_movetype == MOVETYPE_FLY) - set_movetype(pl, MOVETYPE_WALK); - - for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) - { - .entity weaponentity = weaponentities[slot]; - if(!pl.(weaponentity)) - continue; // continue incase other slots exist? - if(pl.(weaponentity).hook) - delete(pl.(weaponentity).hook); - pl.(weaponentity).hook = NULL; - } - - //pl.disableclientprediction = false; -} - -void RemoveHook(entity this) -{ - entity player = this.realowner; - .entity weaponentity = this.weaponentity_fld; - - if(player.(weaponentity).hook == this) - player.(weaponentity).hook = NULL; - - if(player.move_movetype == MOVETYPE_FLY) - set_movetype(player, MOVETYPE_WALK); - delete(this); -} - -void GrapplingHookReset(entity this) -{ - RemoveHook(this); -} - -void GrapplingHook_Stop(entity this) -{ - Send_Effect(EFFECT_HOOK_IMPACT, this.origin, '0 0 0', 1); - sound (this, CH_SHOTS, SND_HOOK_IMPACT, VOL_BASE, ATTEN_NORM); - - this.state = 1; - setthink(this, GrapplingHookThink); - this.nextthink = time; - settouch(this, func_null); - this.velocity = '0 0 0'; - set_movetype(this, MOVETYPE_NONE); - this.hook_length = -1; -} - -.vector hook_start, hook_end; -bool GrapplingHookSend(entity this, entity to, int sf) -{ - WriteHeader(MSG_ENTITY, ENT_CLIENT_HOOK); - sf = sf & 0x7F; - if(sound_allowed(MSG_BROADCAST, this.realowner)) - sf |= 0x80; - WriteByte(MSG_ENTITY, sf); - if(sf & 1) - { - WriteByte(MSG_ENTITY, etof(this.realowner)); - WriteByte(MSG_ENTITY, weaponslot(this.weaponentity_fld)); - } - if(sf & 2) - { - WriteVector(MSG_ENTITY, this.hook_start); - } - if(sf & 4) - { - WriteVector(MSG_ENTITY, this.hook_end); - } - return true; -} - -int autocvar_g_grappling_hook_tarzan; - -void GrapplingHookThink(entity this) -{ - float spd, dist, minlength, pullspeed, ropestretch, ropeairfriction, rubberforce, newlength, rubberforce_overstretch; - vector dir, org, end, v0, dv, v, myorg, vs; - .entity weaponentity = this.weaponentity_fld; - if(this.realowner.(weaponentity).hook != this) // how did that happen? - { - error("Owner lost the hook!\n"); - return; - } - if(LostMovetypeFollow(this) || game_stopped || (round_handler_IsActive() && !round_handler_IsRoundStarted()) || ((this.aiment.flags & FL_PROJECTILE) && this.aiment.classname != "nade")) - { - RemoveHook(this); - return; - } - if(this.aiment) - WarpZone_RefSys_AddIncrementally(this, this.aiment); - - this.nextthink = time; - - int s = W_GunAlign(this.realowner.(weaponentity), STAT(GUNALIGN, this.realowner)) - 1; - vs = hook_shotorigin[s]; - - makevectors(this.realowner.v_angle); - org = this.realowner.origin + this.realowner.view_ofs + v_forward * vs.x + v_right * -vs.y + v_up * vs.z; - myorg = WarpZone_RefSys_TransformOrigin(this.realowner, this, org); - - if(this.hook_length < 0) - this.hook_length = vlen(myorg - this.origin); - - int tarzan = autocvar_g_grappling_hook_tarzan; - entity pull_entity = this.realowner; - float velocity_multiplier = 1; - MUTATOR_CALLHOOK(GrappleHookThink, this, tarzan, pull_entity, velocity_multiplier); - tarzan = M_ARGV(1, int); - pull_entity = M_ARGV(2, entity); - velocity_multiplier = M_ARGV(3, float); - - if(this.state == 1) - { - pullspeed = autocvar_g_balance_grapplehook_speed_pull;//2000; - // speed the rope is pulled with - - rubberforce = autocvar_g_balance_grapplehook_force_rubber;//2000; - // force the rope will use if it is stretched - - rubberforce_overstretch = autocvar_g_balance_grapplehook_force_rubber_overstretch;//1000; - // force the rope will use if it is stretched - - minlength = autocvar_g_balance_grapplehook_length_min;//100; - // minimal rope length - // if the rope goes below this length, it isn't pulled any more - - ropestretch = autocvar_g_balance_grapplehook_stretch;//400; - // if the rope is stretched by more than this amount, more rope is - // given to you again - - ropeairfriction = autocvar_g_balance_grapplehook_airfriction;//0.2 - // while hanging on the rope, this friction component will help you a - // bit to control the rope - - bool frozen_pulling = (autocvar_g_grappling_hook_tarzan >= 2 && autocvar_g_balance_grapplehook_pull_frozen); - - dir = this.origin - myorg; - dist = vlen(dir); - dir = normalize(dir); - - if(tarzan) - { - v = v0 = WarpZone_RefSys_TransformVelocity(pull_entity, this, pull_entity.velocity); - - // first pull the rope... - if(this.realowner.(weaponentity).hook_state & HOOK_PULLING) - { - newlength = this.hook_length; - newlength = max(newlength - pullspeed * frametime, minlength); - - if(newlength < dist - ropestretch) // overstretched? - { - newlength = dist - ropestretch; - if(v * dir < 0) // only if not already moving in hook direction - v = v + frametime * dir * rubberforce_overstretch; - } - - this.hook_length = newlength; - } - - if(pull_entity.move_movetype == MOVETYPE_FLY) - set_movetype(pull_entity, MOVETYPE_WALK); - - if(this.realowner.(weaponentity).hook_state & HOOK_RELEASING) - { - newlength = dist; - this.hook_length = newlength; - } - else - { - // then pull the player - spd = bound(0, (dist - this.hook_length) / ropestretch, 1); - v = v * (1 - frametime * ropeairfriction); - v = v + frametime * dir * spd * rubberforce; - - dv = ((v - v0) * dir) * dir; - if(tarzan >= 2) - { - if(this.aiment.move_movetype == MOVETYPE_WALK || this.aiment.classname == "nade") - { - entity aim_ent = ((IS_VEHICLE(this.aiment) && this.aiment.owner) ? this.aiment.owner : this.aiment); - v = v - dv * 0.5; - if((frozen_pulling && STAT(FROZEN, this.aiment)) || !frozen_pulling) - { - this.aiment.velocity = this.aiment.velocity - dv * 0.5; - UNSET_ONGROUND(this.aiment); - if(this.aiment.flags & FL_PROJECTILE) - UpdateCSQCProjectile(this.aiment); - } - if(this.aiment.classname == "nade") - this.aiment.nextthink = time + autocvar_g_balance_grapplehook_nade_time; // set time after letting go? - aim_ent.pusher = this.realowner; - aim_ent.pushltime = time + autocvar_g_maxpushtime; - aim_ent.istypefrag = PHYS_INPUT_BUTTON_CHAT(aim_ent); - } - } - - UNSET_ONGROUND(pull_entity); - } - - if(!frozen_pulling && !(this.aiment.flags & FL_PROJECTILE)) - pull_entity.velocity = WarpZone_RefSys_TransformVelocity(this, pull_entity, v * velocity_multiplier); - - if(frozen_pulling && autocvar_g_balance_grapplehook_pull_frozen == 2 && !STAT(FROZEN, this.aiment)) - { - RemoveHook(this); - return; - } - } - else - { - end = this.origin - dir*50; - dist = vlen(end - myorg); - if(dist < 200) - spd = dist * (pullspeed / 200); - else - spd = pullspeed; - if(spd < 50) - spd = 0; - this.realowner.velocity = dir*spd; - set_movetype(this.realowner, MOVETYPE_FLY); - - UNSET_ONGROUND(this.realowner); - } - } - - makevectors(this.angles.x * '-1 0 0' + this.angles.y * '0 1 0'); - myorg = WarpZone_RefSys_TransformOrigin(this, this.realowner, this.origin); // + v_forward * (-9); - - if(myorg != this.hook_start) - { - this.SendFlags |= 2; - this.hook_start = myorg; - } - if(org != this.hook_end) - { - this.SendFlags |= 4; - this.hook_end = org; - } -} - -void GrapplingHookTouch(entity this, entity toucher) -{ - if(toucher.move_movetype == MOVETYPE_FOLLOW) - return; - PROJECTILE_TOUCH(this, toucher); - - GrapplingHook_Stop(this); - - if(toucher) - //if(toucher.move_movetype != MOVETYPE_NONE) - { - SetMovetypeFollow(this, toucher); - WarpZone_RefSys_BeginAddingIncrementally(this, this.aiment); - } - - //this.realowner.disableclientprediction = true; -} - -void GrapplingHook_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force) -{ - if(GetResource(this, RES_HEALTH) <= 0) - return; - - if (!W_CheckProjectileDamage(inflictor.realowner, this.realowner, deathtype, -1)) // no exceptions - return; // g_balance_projectiledamage says to halt - - TakeResource(this, RES_HEALTH, damage); - - if (GetResource(this, RES_HEALTH) <= 0) - { - if(attacker != this.realowner) - { - this.realowner.pusher = attacker; - this.realowner.pushltime = time + autocvar_g_maxpushtime; - this.realowner.istypefrag = PHYS_INPUT_BUTTON_CHAT(this.realowner); - } - RemoveHook(this); - } -} - -void FireGrapplingHook(entity actor, .entity weaponentity) -{ - if(weaponLocked(actor)) return; - if(actor.vehicle) return; - - int s = W_GunAlign(actor.(weaponentity), STAT(GUNALIGN, actor)) - 1; - vector vs = hook_shotorigin[s]; - vector oldmovedir = actor.(weaponentity).movedir; - actor.(weaponentity).movedir = vs; - W_SetupShot_ProjectileSize(actor, weaponentity, '-3 -3 -3', '3 3 3', true, 0, SND_HOOK_FIRE, CH_WEAPON_B, 0, WEP_HOOK.m_id); - W_MuzzleFlash(WEP_HOOK, actor, weaponentity, w_shotorg, '0 0 0'); - actor.(weaponentity).movedir = oldmovedir; - - entity missile = WarpZone_RefSys_SpawnSameRefSys(actor); - missile.owner = missile.realowner = actor; - actor.(weaponentity).hook = missile; - missile.weaponentity_fld = weaponentity; - missile.reset = GrapplingHookReset; - missile.classname = "grapplinghook"; - missile.flags = FL_PROJECTILE; - IL_PUSH(g_projectiles, missile); - IL_PUSH(g_bot_dodge, missile); - - set_movetype(missile, ((autocvar_g_balance_grapplehook_gravity) ? MOVETYPE_TOSS : MOVETYPE_FLY)); - PROJECTILE_MAKETRIGGER(missile); - - //setmodel (missile, MDL_HOOK); // precision set below - setsize (missile, '-3 -3 -3', '3 3 3'); - setorigin(missile, w_shotorg); - - missile.state = 0; // not latched onto anything - - W_SetupProjVelocity_Explicit(missile, w_shotdir, v_up, autocvar_g_balance_grapplehook_speed_fly, 0, 0, 0, false); - - missile.angles = vectoangles (missile.velocity); - //missile.glow_color = 250; // 244, 250 - //missile.glow_size = 120; - settouch(missile, GrapplingHookTouch); - setthink(missile, GrapplingHookThink); - missile.nextthink = time; - - missile.effects = /*EF_FULLBRIGHT | EF_ADDITIVE |*/ EF_LOWPRECISION; - - SetResourceExplicit(missile, RES_HEALTH, autocvar_g_balance_grapplehook_health); - missile.event_damage = GrapplingHook_Damage; - missile.takedamage = DAMAGE_AIM; - missile.damageforcescale = 0; - missile.damagedbycontents = (autocvar_g_balance_grapplehook_damagedbycontents); - if(missile.damagedbycontents) - IL_PUSH(g_damagedbycontents, missile); - - missile.hook_start = missile.hook_end = missile.origin; - - Net_LinkEntity(missile, false, 0, GrapplingHookSend); -} - -void GrappleHookInit() -{ - if(g_grappling_hook) - { - hook_shotorigin[0] = '8 8 -12'; - hook_shotorigin[1] = '8 8 -12'; - hook_shotorigin[2] = '8 8 -12'; - hook_shotorigin[3] = '8 8 -12'; - } - else - { - Weapon w = WEP_HOOK; - w.wr_init(w); - hook_shotorigin[0] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_HOOK.m_id), false, false, 1); - hook_shotorigin[1] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_HOOK.m_id), false, false, 2); - hook_shotorigin[2] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_HOOK.m_id), false, false, 3); - hook_shotorigin[3] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_HOOK.m_id), false, false, 4); - } -} diff --git a/qcsrc/server/g_hook.qh b/qcsrc/server/g_hook.qh deleted file mode 100644 index 1ed78e274..000000000 --- a/qcsrc/server/g_hook.qh +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -// Wazat's grappling hook -.entity hook; -void GrapplingHookThink(entity this); -void RemoveGrapplingHooks(entity pl); -void RemoveHook(entity this); -// (note: you can change the hook impulse #'s to whatever you please) -.float hook_time; - -.float hook_length; - -const float HOOK_FIRING = BIT(0); -const float HOOK_REMOVING = BIT(1); -const float HOOK_PULLING = BIT(2); -const float HOOK_RELEASING = BIT(3); -const float HOOK_WAITING_FOR_RELEASE = BIT(4); -.float hook_state; -.int state; - -void GrappleHookInit(); -vector hook_shotorigin[4]; - diff --git a/qcsrc/server/g_world.qc b/qcsrc/server/g_world.qc deleted file mode 100644 index e258e351a..000000000 --- a/qcsrc/server/g_world.qc +++ /dev/null @@ -1,2266 +0,0 @@ -#include "g_world.qh" - -#include "anticheat.qh" -#include "antilag.qh" -#include "bot/api.qh" -#include "campaign.qh" -#include "cheats.qh" -#include "client.qh" -#include "command/common.qh" -#include "command/getreplies.qh" -#include "command/sv_cmd.qh" -#include "command/vote.qh" -#include "g_hook.qh" -#include -#include -#include "ipban.qh" -#include "mapvoting.qh" -#include -#include "race.qh" -#include "scores.qh" -#include "scores_rules.qh" -#include "spawnpoints.qh" -#include "teamplay.qh" -#include "weapons/weaponstats.qh" -#include -#include "../common/constants.qh" -#include -#include "../common/deathtypes/all.qh" -#include -#include "../common/gamemodes/sv_rules.qh" -#include "../common/mapinfo.qh" -#include "../common/monsters/_mod.qh" -#include "../common/monsters/sv_monsters.qh" -#include "../common/vehicles/all.qh" -#include "../common/notifications/all.qh" -#include "../common/physics/player.qh" -#include "../common/playerstats.qh" -#include "../common/stats.qh" -#include "../common/teams.qh" -#include -#include "../common/mapobjects/trigger/secret.qh" -#include "../common/mapobjects/target/music.qh" -#include "../common/util.qh" -#include "../common/items/_mod.qh" -#include -#include "../common/state.qh" - -const float LATENCY_THINKRATE = 10; -.float latency_sum; -.float latency_cnt; -.float latency_time; -entity pingplreport; -void PingPLReport_Think(entity this) -{ - float delta; - entity e; - - delta = 3 / maxclients; - if(delta < sys_frametime) - delta = 0; - this.nextthink = time + delta; - - e = edict_num(this.cnt + 1); - if(IS_CLIENT(e) && IS_REAL_CLIENT(e)) - { - WriteHeader(MSG_BROADCAST, TE_CSQC_PINGPLREPORT); - WriteByte(MSG_BROADCAST, this.cnt); - WriteShort(MSG_BROADCAST, bound(1, CS(e).ping, 65535)); - WriteByte(MSG_BROADCAST, min(ceil(CS(e).ping_packetloss * 255), 255)); - WriteByte(MSG_BROADCAST, min(ceil(CS(e).ping_movementloss * 255), 255)); - - // record latency times for clients throughout the match so we can report it to playerstats - if(time > (CS(e).latency_time + LATENCY_THINKRATE)) - { - CS(e).latency_sum += CS(e).ping; - CS(e).latency_cnt += 1; - CS(e).latency_time = time; - //print("sum: ", ftos(CS(e).latency_sum), ", cnt: ", ftos(CS(e).latency_cnt), ", avg: ", ftos(CS(e).latency_sum / CS(e).latency_cnt), ".\n"); - } - } - else - { - WriteHeader(MSG_BROADCAST, TE_CSQC_PINGPLREPORT); - WriteByte(MSG_BROADCAST, this.cnt); - WriteShort(MSG_BROADCAST, 0); - WriteByte(MSG_BROADCAST, 0); - WriteByte(MSG_BROADCAST, 0); - } - this.cnt = (this.cnt + 1) % maxclients; -} -void PingPLReport_Spawn() -{ - pingplreport = new_pure(pingplreport); - setthink(pingplreport, PingPLReport_Think); - pingplreport.nextthink = time; -} - -const float SPAWNFLAG_NO_WAYPOINTS_FOR_ITEMS = 1; -string redirection_target; -float world_initialized; - -void SetDefaultAlpha() -{ - if (!MUTATOR_CALLHOOK(SetDefaultAlpha)) - { - default_player_alpha = autocvar_g_player_alpha; - if(default_player_alpha == 0) - default_player_alpha = 1; - default_weapon_alpha = default_player_alpha; - } -} - -void GotoFirstMap(entity this) -{ - float n; - if(autocvar__sv_init) - { - // cvar_set("_sv_init", "0"); - // we do NOT set this to 0 any more, so someone "accidentally" changing - // to this "init" map on a dedicated server will cause no permanent - // harm - if(autocvar_g_maplist_shuffle) - ShuffleMaplist(); - n = tokenizebyseparator(autocvar_g_maplist, " "); - cvar_set("g_maplist_index", ftos(n - 1)); // jump to map 0 in GotoNextMap - - MapInfo_Enumerate(); - MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags(), 0); - - if(!DoNextMapOverride(1)) - GotoNextMap(1); - - return; - } - - if(time < 5) - { - this.nextthink = time; - } - else - { - this.nextthink = time + 1; - LOG_INFO("Waiting for _sv_init being set to 1 by initialization scripts..."); - } -} - -void cvar_changes_init() -{ - float h; - string k, v, d; - float n, i, adding, pureadding; - - strfree(cvar_changes); - strfree(cvar_purechanges); - cvar_purechanges_count = 0; - - h = buf_create(); - buf_cvarlist(h, "", "_"); // exclude all _ cvars as they are temporary - n = buf_getsize(h); - - adding = true; - pureadding = true; - - for(i = 0; i < n; ++i) - { - k = bufstr_get(h, i); - -#define BADPREFIX(p) if(substring(k, 0, strlen(p)) == p) continue -#define BADPRESUFFIX(p,s) if(substring(k, 0, strlen(p)) == p && substring(k, -strlen(s), -1) == s) continue -#define BADCVAR(p) if(k == p) continue - - // general excludes and namespaces for server admin used cvars - BADPREFIX("help_"); // PN's server has this listed as changed, let's not rat him out for THAT - - // internal - BADPREFIX("csqc_"); - BADPREFIX("cvar_check_"); - BADCVAR("gamecfg"); - BADCVAR("g_configversion"); - BADCVAR("halflifebsp"); - BADCVAR("sv_mapformat_is_quake2"); - BADCVAR("sv_mapformat_is_quake3"); - BADPREFIX("sv_world"); - - // client - BADPREFIX("chase_"); - BADPREFIX("cl_"); - BADPREFIX("con_"); - BADPREFIX("scoreboard_"); - BADPREFIX("g_campaign"); - BADPREFIX("g_waypointsprite_"); - BADPREFIX("gl_"); - BADPREFIX("joy"); - BADPREFIX("hud_"); - BADPREFIX("m_"); - BADPREFIX("menu_"); - BADPREFIX("net_slist_"); - BADPREFIX("r_"); - BADPREFIX("sbar_"); - BADPREFIX("scr_"); - BADPREFIX("snd_"); - BADPREFIX("show"); - BADPREFIX("sensitivity"); - BADPREFIX("userbind"); - BADPREFIX("v_"); - BADPREFIX("vid_"); - BADPREFIX("crosshair"); - BADCVAR("mod_q3bsp_lightmapmergepower"); - BADCVAR("mod_q3bsp_nolightmaps"); - BADCVAR("fov"); - BADCVAR("mastervolume"); - BADCVAR("volume"); - BADCVAR("bgmvolume"); - BADCVAR("in_pitch_min"); - BADCVAR("in_pitch_max"); - - // private - BADCVAR("developer"); - BADCVAR("log_dest_udp"); - BADCVAR("net_address"); - BADCVAR("net_address_ipv6"); - BADCVAR("port"); - BADCVAR("savedgamecfg"); - BADCVAR("serverconfig"); - BADCVAR("sv_autoscreenshot"); - BADCVAR("sv_heartbeatperiod"); - BADCVAR("sv_vote_master_password"); - BADCVAR("sys_colortranslation"); - BADCVAR("sys_specialcharactertranslation"); - BADCVAR("timeformat"); - BADCVAR("timestamps"); - BADCVAR("g_require_stats"); - BADPREFIX("developer_"); - BADPREFIX("g_ban_"); - BADPREFIX("g_banned_list"); - BADPREFIX("g_require_stats_"); - BADPREFIX("g_chat_flood_"); - BADPREFIX("g_ghost_items"); - BADPREFIX("g_playerstats_"); - BADPREFIX("g_voice_flood_"); - BADPREFIX("log_file"); - BADPREFIX("quit_"); - BADPREFIX("rcon_"); - BADPREFIX("sv_allowdownloads"); - BADPREFIX("sv_autodemo"); - BADPREFIX("sv_curl_"); - BADPREFIX("sv_eventlog"); - BADPREFIX("sv_logscores_"); - BADPREFIX("sv_master"); - BADPREFIX("sv_weaponstats_"); - BADPREFIX("sv_waypointsprite_"); - BADCVAR("rescan_pending"); - - // these can contain player IDs, so better hide - BADPREFIX("g_forced_team_"); - BADCVAR("sv_muteban_list"); - BADCVAR("sv_voteban_list"); - BADCVAR("sv_allow_customplayermodels_idlist"); - BADCVAR("sv_allow_customplayermodels_speciallist"); - - // mapinfo - BADCVAR("fraglimit"); - BADCVAR("g_arena"); - BADCVAR("g_assault"); - BADCVAR("g_ca"); - BADCVAR("g_ca_teams"); - BADCVAR("g_conquest"); - BADCVAR("g_conquest_teams"); - BADCVAR("g_ctf"); - BADCVAR("g_cts"); - BADCVAR("g_dotc"); - BADCVAR("g_dm"); - BADCVAR("g_domination"); - BADCVAR("g_domination_default_teams"); - BADCVAR("g_duel"); - BADCVAR("g_duel_not_dm_maps"); - BADCVAR("g_freezetag"); - BADCVAR("g_freezetag_teams"); - BADCVAR("g_invasion_teams"); - BADCVAR("g_invasion_type"); - BADCVAR("g_jailbreak"); - BADCVAR("g_jailbreak_teams"); - BADCVAR("g_keepaway"); - BADCVAR("g_keyhunt"); - BADCVAR("g_keyhunt_teams"); - BADCVAR("g_lms"); - BADCVAR("g_nexball"); - BADCVAR("g_onslaught"); - BADCVAR("g_race"); - BADCVAR("g_race_laps_limit"); - BADCVAR("g_race_qualifying_timelimit"); - BADCVAR("g_race_qualifying_timelimit_override"); - BADCVAR("g_runematch"); - BADCVAR("g_shootfromeye"); - BADCVAR("g_snafu"); - BADCVAR("g_survival"); - BADCVAR("g_survival_not_dm_maps"); - BADCVAR("g_tdm"); - BADCVAR("g_tdm_on_dm_maps"); - BADCVAR("g_tdm_teams"); - BADCVAR("g_vip"); - BADCVAR("leadlimit"); - BADCVAR("nextmap"); - BADCVAR("teamplay"); - BADCVAR("timelimit"); - BADCVAR("g_mapinfo_settemp_acl"); - BADCVAR("g_mapinfo_ignore_warnings"); - BADCVAR("g_maplist_ignore_sizes"); - BADCVAR("g_maplist_sizes_count_bots"); - - // long - BADCVAR("hostname"); - BADCVAR("g_maplist"); - BADCVAR("g_maplist_mostrecent"); - BADCVAR("sv_motd"); - - v = cvar_string(k); - d = cvar_defstring(k); - if(v == d) - continue; - - if(adding) - { - cvar_changes = strcat(cvar_changes, k, " \"", v, "\" // \"", d, "\"\n"); - if(strlen(cvar_changes) > 16384) - { - cvar_changes = "// too many settings have been changed to show them here\n"; - adding = 0; - } - } - - // now check if the changes are actually gameplay relevant - - // does nothing gameplay relevant - BADCVAR("captureleadlimit_override"); - BADCVAR("condump_stripcolors"); - BADCVAR("gameversion"); - BADCVAR("fs_gamedir"); - BADCVAR("g_allow_oldvortexbeam"); - BADCVAR("g_balance_kill_delay"); - BADCVAR("g_buffs_pickup_anyway"); - BADCVAR("g_buffs_randomize"); - BADCVAR("g_buffs_randomize_teamplay"); - BADCVAR("g_campcheck_distance"); - BADCVAR("g_chatsounds"); - BADCVAR("g_ca_point_leadlimit"); - BADCVAR("g_ca_point_limit"); - BADCVAR("g_ctf_captimerecord_always"); - BADCVAR("g_ctf_flag_glowtrails"); - BADCVAR("g_ctf_dynamiclights"); - BADCVAR("g_ctf_flag_pickup_verbosename"); - BADPRESUFFIX("g_ctf_flag_", "_model"); - BADPRESUFFIX("g_ctf_flag_", "_skin"); - BADCVAR("g_domination_point_leadlimit"); - BADCVAR("g_forced_respawn"); - BADCVAR("g_freezetag_point_leadlimit"); - BADCVAR("g_freezetag_point_limit"); - BADCVAR("g_glowtrails"); - BADCVAR("g_hats"); - BADCVAR("g_casings"); - BADCVAR("g_invasion_point_limit"); - BADCVAR("g_jump_grunt"); - BADCVAR("g_keepaway_ballcarrier_effects"); - BADCVAR("g_keepawayball_effects"); - BADCVAR("g_keyhunt_point_leadlimit"); - BADCVAR("g_nexball_goalleadlimit"); - BADCVAR("g_new_toys_autoreplace"); - BADCVAR("g_new_toys_use_pickupsound"); - BADCVAR("g_physics_predictall"); - BADCVAR("g_piggyback"); - BADCVAR("g_playerclip_collisions"); - BADCVAR("g_spawn_alloweffects"); - BADCVAR("g_tdm_point_leadlimit"); - BADCVAR("g_tdm_point_limit"); - BADCVAR("leadlimit_and_fraglimit"); - BADCVAR("leadlimit_override"); - BADCVAR("pausable"); - BADCVAR("sv_announcer"); - BADCVAR("sv_checkforpacketsduringsleep"); - BADCVAR("sv_damagetext"); - BADCVAR("sv_db_saveasdump"); - BADCVAR("sv_intermission_cdtrack"); - BADCVAR("sv_mapchange_delay"); - BADCVAR("sv_minigames"); - BADCVAR("sv_namechangetimer"); - BADCVAR("sv_precacheplayermodels"); - BADCVAR("sv_radio"); - BADCVAR("sv_stepheight"); - BADCVAR("sv_timeout"); - BADCVAR("sv_weapons_modeloverride"); - BADCVAR("w_prop_interval"); - BADPREFIX("chat_"); - BADPREFIX("crypto_"); - BADPREFIX("gameversion_"); - BADPREFIX("g_chat_"); - BADPREFIX("g_ctf_captimerecord_"); - BADPREFIX("g_hats_"); - BADPREFIX("g_maplist_"); - BADPREFIX("g_mod_"); - BADPREFIX("g_respawn_"); - BADPREFIX("net_"); - BADPREFIX("notification_"); - BADPREFIX("prvm_"); - BADPREFIX("skill_"); - BADPREFIX("sv_allow_"); - BADPREFIX("sv_cullentities_"); - BADPREFIX("sv_maxidle_"); - BADPREFIX("sv_minigames_"); - BADPREFIX("sv_radio_"); - BADPREFIX("sv_timeout_"); - BADPREFIX("sv_vote_"); - BADPREFIX("timelimit_"); - - // allowed changes to server admins (please sync this to server.cfg) - // vi commands: - // :/"impure"/,$d - // :g!,^\/\/[^ /],d - // :%s,//\([^ ]*\).*,BADCVAR("\1");, - // :%!sort - // yes, this does contain some redundant stuff, don't really care - BADPREFIX("bot_ai_"); - BADCVAR("bot_config_file"); - BADCVAR("bot_number"); - BADCVAR("bot_prefix"); - BADCVAR("bot_suffix"); - BADCVAR("capturelimit_override"); - BADCVAR("fraglimit_override"); - BADCVAR("gametype"); - BADCVAR("g_antilag"); - BADCVAR("g_balance_teams"); - BADCVAR("g_balance_teams_prevent_imbalance"); - BADCVAR("g_balance_teams_scorefactor"); - BADCVAR("g_ban_sync_trusted_servers"); - BADCVAR("g_ban_sync_uri"); - BADCVAR("g_buffs"); - BADCVAR("g_ca_teams_override"); - BADCVAR("g_ctf_fullbrightflags"); - BADCVAR("g_ctf_ignore_frags"); - BADCVAR("g_ctf_leaderboard"); - BADCVAR("g_domination_point_limit"); - BADCVAR("g_domination_teams_override"); - BADCVAR("g_freezetag_teams_override"); - BADCVAR("g_friendlyfire"); - BADCVAR("g_fullbrightitems"); - BADCVAR("g_fullbrightplayers"); - BADCVAR("g_keyhunt_point_limit"); - BADCVAR("g_keyhunt_teams_override"); - BADCVAR("g_lms_lives_override"); - BADCVAR("g_maplist"); - BADCVAR("g_maxplayers"); - BADCVAR("g_mirrordamage"); - BADCVAR("g_nexball_goallimit"); - BADCVAR("g_norecoil"); - BADCVAR("g_physics_clientselect"); - BADCVAR("g_pinata"); - BADCVAR("g_powerups"); - BADCVAR("g_player_brightness"); - BADCVAR("g_rocket_flying"); - BADCVAR("g_rocket_flying_disabledelays"); - BADCVAR("g_spawnshieldtime"); - BADCVAR("g_start_delay"); - BADCVAR("g_superspectate"); - BADCVAR("g_tdm_teams_override"); - BADCVAR("g_warmup"); - BADCVAR("g_weapon_stay"); BADPRESUFFIX("g_", "_weapon_stay"); - BADCVAR("hostname"); - BADCVAR("log_file"); - BADCVAR("maxplayers"); - BADCVAR("minplayers"); - BADCVAR("minplayers_per_team"); - BADCVAR("net_address"); - BADCVAR("port"); - BADCVAR("rcon_password"); - BADCVAR("rcon_restricted_commands"); - BADCVAR("rcon_restricted_password"); - BADCVAR("skill"); - BADCVAR("sv_adminnick"); - BADCVAR("sv_autoscreenshot"); - BADCVAR("sv_autotaunt"); - BADCVAR("sv_curl_defaulturl"); - BADCVAR("sv_defaultcharacter"); - BADCVAR("sv_defaultcharacterskin"); - BADCVAR("sv_defaultplayercolors"); - BADCVAR("sv_defaultplayermodel"); - BADCVAR("sv_defaultplayerskin"); - BADCVAR("sv_maxidle"); - BADCVAR("sv_maxrate"); - BADCVAR("sv_motd"); - BADCVAR("sv_public"); - BADCVAR("sv_ready_restart"); - BADCVAR("sv_status_privacy"); - BADCVAR("sv_taunt"); - BADCVAR("sv_vote_call"); - BADCVAR("sv_vote_commands"); - BADCVAR("sv_vote_majority_factor"); - BADCVAR("sv_vote_master"); - BADCVAR("sv_vote_master_commands"); - BADCVAR("sv_vote_master_password"); - BADCVAR("sv_vote_simple_majority_factor"); - BADCVAR("teamplay_mode"); - BADCVAR("timelimit_override"); - BADPREFIX("g_warmup_"); - BADPREFIX("sv_info_"); - BADPREFIX("sv_ready_restart_"); - - // mutators that announce themselves properly to the server browser - BADCVAR("g_instagib"); - BADCVAR("g_new_toys"); - BADCVAR("g_nix"); - BADCVAR("g_grappling_hook"); - BADCVAR("g_jetpack"); - - // temporary for testing - // TODO remove before 0.8.3 release - BADCVAR("g_ca_weaponarena"); - BADCVAR("g_freezetag_weaponarena"); - BADCVAR("g_lms_weaponarena"); - BADCVAR("g_ctf_stalemate_time"); - - if(cvar_string("g_mod_balance") == "Testing") - { - // (temporary) while using the Testing balance, any weapon balance cvars are allowed to be changed - BADPREFIX("g_balance_"); - } - -#undef BADPRESUFFIX -#undef BADPREFIX -#undef BADCVAR - - if(pureadding) - { - cvar_purechanges = strcat(cvar_purechanges, k, " \"", v, "\" // \"", d, "\"\n"); - if(strlen(cvar_purechanges) > 16384) - { - cvar_purechanges = "// too many settings have been changed to show them here\n"; - pureadding = 0; - } - } - ++cvar_purechanges_count; - // WARNING: this variable is used for the server list - // NEVER dare to skip this code! - // Hacks to intentionally appearing as "pure server" even though you DO have - // modified settings may be punished by removal from the server list. - // You can do to the variables cvar_changes and cvar_purechanges all you want, - // though. - } - buf_del(h); - if(cvar_changes == "") - cvar_changes = "// this server runs at default server settings\n"; - else - cvar_changes = strcat("// this server runs at modified server settings:\n", cvar_changes); - cvar_changes = strzone(cvar_changes); - if(cvar_purechanges == "") - cvar_purechanges = "// this server runs at default gameplay settings\n"; - else - cvar_purechanges = strcat("// this server runs at modified gameplay settings:\n", cvar_purechanges); - cvar_purechanges = strzone(cvar_purechanges); -} - -entity randomseed; -bool RandomSeed_Send(entity this, entity to, int sf) -{ - WriteHeader(MSG_ENTITY, ENT_CLIENT_RANDOMSEED); - WriteShort(MSG_ENTITY, this.cnt); - return true; -} -void RandomSeed_Think(entity this) -{ - this.cnt = bound(0, floor(random() * 65536), 65535); - this.nextthink = time + 5; - - this.SendFlags |= 1; -} -void RandomSeed_Spawn() -{ - randomseed = new_pure(randomseed); - setthink(randomseed, RandomSeed_Think); - Net_LinkEntity(randomseed, false, 0, RandomSeed_Send); - - getthink(randomseed)(randomseed); // sets random seed and nextthink -} - -spawnfunc(__init_dedicated_server) -{ - // handler for _init/_init map (only for dedicated server initialization) - - world_initialized = -1; // don't complain - - delete_fn = remove_unsafely; - - entity e = spawn(); - setthink(e, GotoFirstMap); - e.nextthink = time; // this is usually 1 at this point - - e = new(info_player_deathmatch); // safeguard against player joining - - // assign reflectively to avoid "assignment to world" warning - for (int i = 0, n = numentityfields(); i < n; ++i) { - string k = entityfieldname(i); - if (k == "classname") { - // safeguard against various stuff ;) - putentityfieldstring(i, this, "worldspawn"); - break; - } - } - - // needs to be done so early because of the constants they create - static_init(); - static_init_late(); - static_init_precache(); - - IL_PUSH(g_spawnpoints, e); // just incase - - MapInfo_Enumerate(); - MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags(), 0); -} - -void __init_dedicated_server_shutdown() { - MapInfo_Shutdown(); -} - -STATIC_INIT_EARLY(maxclients) -{ - maxclients = 0; - for (entity head = nextent(NULL); head; head = nextent(head)) { - ++maxclients; - } -} - -void default_delayedinit(entity this) -{ - if(!scores_initialized) - ScoreRules_generic(); -} - -void InitGameplayMode() -{ - VoteReset(); - - // find out good world mins/maxs bounds, either the static bounds found by looking for solid, or the mapinfo specified bounds - get_mi_min_max(1); - // assign reflectively to avoid "assignment to world" warning - int done = 0; for (int i = 0, n = numentityfields(); i < n; ++i) { - string k = entityfieldname(i); vector v = (k == "mins") ? mi_min : (k == "maxs") ? mi_max : '0 0 0'; - if (v) { - putentityfieldstring(i, world, sprintf("%v", v)); - if (++done == 2) break; - } - } - // currently, NetRadiant's limit is 131072 qu for each side - // distance from one corner of a 131072qu cube to the opposite corner is approx. 227023 qu - // set the distance according to map size but don't go over the limit to avoid issues with float precision - // in case somebody makes extremely large maps - max_shot_distance = min(230000, vlen(world.maxs - world.mins)); - - MapInfo_LoadMapSettings(mapname); - GameRules_teams(false); - - if (!cvar_value_issafe(world.fog)) - { - LOG_INFO("The current map contains a potentially harmful fog setting, ignored"); - world.fog = string_null; - } - if(MapInfo_Map_fog != "") - { - if(MapInfo_Map_fog == "none") - world.fog = string_null; - else - world.fog = strzone(MapInfo_Map_fog); - } - clientstuff = strzone(MapInfo_Map_clientstuff); - - MapInfo_ClearTemps(); - - gamemode_name = MapInfo_Type_ToText(MapInfo_LoadedGametype); - - cache_mutatormsg = strzone(""); - cache_lastmutatormsg = strzone(""); - - InitializeEntity(NULL, default_delayedinit, INITPRIO_GAMETYPE_FALLBACK); -} - -void Map_MarkAsRecent(string m); -float world_already_spawned; -spawnfunc(worldspawn) -{ - server_is_dedicated = boolean(stof(cvar_defstring("is_dedicated"))); - - bool wantrestart = false; - { - if (!server_is_dedicated) - { - // force unloading of server pk3 files when starting a listen server - // localcmd("\nfs_rescan\n"); // FIXME: does more harm than good, has unintended side effects. What we really want is to unload temporary pk3s only - // restore csqc_progname too - string expect = "csprogs.dat"; - wantrestart = cvar_string("csqc_progname") != expect; - cvar_set("csqc_progname", expect); - } - else - { - // Try to use versioned csprogs from pk3 - // Only ever use versioned csprogs.dat files on dedicated servers; - // we need to reset csqc_progname on clients ourselves, and it's easier if the client's release name is constant - string pk3csprogs = "csprogs-" WATERMARK ".dat"; - // This always works; fall back to it if a versioned csprogs.dat is suddenly missing - string select = "csprogs.dat"; - if (fexists(pk3csprogs)) select = pk3csprogs; - if (cvar_string("csqc_progname") != select) - { - cvar_set("csqc_progname", select); - wantrestart = true; - } - // Check for updates on startup - // We do it this way for atomicity so that connecting clients still match the server progs and don't disconnect - int sentinel = fopen("progs.txt", FILE_READ); - if (sentinel >= 0) - { - string switchversion = fgets(sentinel); - fclose(sentinel); - if (switchversion != "" && switchversion != WATERMARK) - { - LOG_INFOF("Switching progs: " WATERMARK " -> %s", switchversion); - // if it doesn't exist, assume either: - // a) the current program was overwritten - // b) this is a client only update - string newprogs = sprintf("progs-%s.dat", switchversion); - if (fexists(newprogs)) - { - cvar_set("sv_progs", newprogs); - wantrestart = true; - } - string newcsprogs = sprintf("csprogs-%s.dat", switchversion); - if (fexists(newcsprogs)) - { - cvar_set("csqc_progname", newcsprogs); - wantrestart = true; - } - } - } - } - if (wantrestart) - { - LOG_INFO("Restart requested"); - changelevel(mapname); - // let initialization continue, shutdown depends on it - } - } - - if(world_already_spawned) - error("world already spawned - you may have EXACTLY ONE worldspawn!"); - world_already_spawned = true; - - delete_fn = remove_safely; // during spawning, watch what you remove! - - cvar_changes_init(); // do this very early now so it REALLY matches the server config - - // needs to be done so early because of the constants they create - static_init(); - - ServerProgsDB = db_load(strcat("server.db", autocvar_sessionid)); - - TemporaryDB = db_create(); - - // 0 normal - lightstyle(0, "m"); - - // 1 FLICKER (first variety) - lightstyle(1, "mmnmmommommnonmmonqnmmo"); - - // 2 SLOW STRONG PULSE - lightstyle(2, "abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba"); - - // 3 CANDLE (first variety) - lightstyle(3, "mmmmmaaaaammmmmaaaaaabcdefgabcdefg"); - - // 4 FAST STROBE - lightstyle(4, "mamamamamama"); - - // 5 GENTLE PULSE 1 - lightstyle(5,"jklmnopqrstuvwxyzyxwvutsrqponmlkj"); - - // 6 FLICKER (second variety) - lightstyle(6, "nmonqnmomnmomomno"); - - // 7 CANDLE (second variety) - lightstyle(7, "mmmaaaabcdefgmmmmaaaammmaamm"); - - // 8 CANDLE (third variety) - lightstyle(8, "mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa"); - - // 9 SLOW STROBE (fourth variety) - lightstyle(9, "aaaaaaaazzzzzzzz"); - - // 10 FLUORESCENT FLICKER - lightstyle(10, "mmamammmmammamamaaamammma"); - - // 11 SLOW PULSE NOT FADE TO BLACK - lightstyle(11, "abcdefghijklmnopqrrqponmlkjihgfedcba"); - - // styles 32-62 are assigned by the spawnfunc_light program for switchable lights - - // 63 testing - lightstyle(63, "a"); - - if(autocvar_g_campaign) - CampaignPreInit(); - - Map_MarkAsRecent(mapname); - - PlayerStats_GameReport_Init(); // we need this to be initiated before InitGameplayMode - - InitGameplayMode(); - static_init_late(); - static_init_precache(); - readlevelcvars(); - GrappleHookInit(); - - GameRules_limit_fallbacks(); - - if(warmup_limit == 0) - warmup_limit = (autocvar_timelimit > 0) ? autocvar_timelimit * 60 : autocvar_timelimit; - - player_count = 0; - bot_waypoints_for_items = autocvar_g_waypoints_for_items; - if(bot_waypoints_for_items == 1) - if(this.spawnflags & SPAWNFLAG_NO_WAYPOINTS_FOR_ITEMS) - bot_waypoints_for_items = 0; - - WaypointSprite_Init(); - - GameLogInit(); // prepare everything - // NOTE for matchid: - // changing the logic generating it is okay. But: - // it HAS to stay <= 64 chars - // character set: ASCII 33-126 without the following characters: : ; ' " \ $ - if(autocvar_sv_eventlog) - { - string s = sprintf("%s.%s.%06d", itos(autocvar_sv_eventlog_files_counter), strftime(false, "%s"), floor(random() * 1000000)); - matchid = strzone(s); - - GameLogEcho(strcat(":gamestart:", GetGametype(), "_", GetMapname(), ":", s)); - s = ":gameinfo:mutators:LIST"; - - MUTATOR_CALLHOOK(BuildMutatorsString, s); - s = M_ARGV(0, string); - - // initialiation stuff, not good in the mutator system - if(!autocvar_g_use_ammunition) - s = strcat(s, ":no_use_ammunition"); - - // initialiation stuff, not good in the mutator system - if(autocvar_g_pickup_items == 0) - s = strcat(s, ":no_pickup_items"); - if(autocvar_g_pickup_items > 0) - s = strcat(s, ":pickup_items"); - - // initialiation stuff, not good in the mutator system - if(autocvar_g_weaponarena != "0") - s = strcat(s, ":", autocvar_g_weaponarena, " arena"); - - // TODO to mutator system - if(autocvar_g_norecoil) - s = strcat(s, ":norecoil"); - - // TODO to mutator system - if(autocvar_g_powerups == 0) - s = strcat(s, ":no_powerups"); - if(autocvar_g_powerups > 0) - s = strcat(s, ":powerups"); - - GameLogEcho(s); - GameLogEcho(":gameinfo:end"); - } - else - matchid = strzone(ftos(random())); - - cvar_set("nextmap", ""); - - SetDefaultAlpha(); - - if(autocvar_g_campaign) - CampaignPostInit(); - - Ban_LoadBans(); - - MapInfo_Enumerate(); - MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags(), 1); - - if(fexists(strcat("scripts/", mapname, ".arena"))) - cvar_settemp("sv_q3acompat_machineshotgunswap", "1"); - - if(fexists(strcat("scripts/", mapname, ".defi"))) - cvar_settemp("sv_q3defragcompat", "1"); - - if(whichpack(strcat("maps/", mapname, ".cfg")) != "") - { - int fd = fopen(strcat("maps/", mapname, ".cfg"), FILE_READ); - if(fd != -1) - { - string s; - while((s = fgets(fd))) - { - int l = tokenize_console(s); - if(l < 2) - continue; - if(argv(0) == "cd") - { - string trackname = argv(2); - LOG_INFO("Found ^1UNSUPPORTED^7 cd loop command in .cfg file; put this line in mapinfo instead:"); - LOG_INFO(" cdtrack ", trackname); - if (cvar_value_issafe(trackname)) - { - string newstuff = strcat(clientstuff, "cd loop \"", trackname, "\"\n"); - strcpy(clientstuff, newstuff); - } - } - else if(argv(0) == "fog") - { - LOG_INFO("Found ^1UNSUPPORTED^7 fog command in .cfg file; put this line in worldspawn in the .map/.bsp/.ent file instead:"); - LOG_INFO(" \"fog\" \"", s, "\""); - } - else if(argv(0) == "set") - { - LOG_INFO("Found ^1UNSUPPORTED^7 set command in .cfg file; put this line in mapinfo instead:"); - LOG_INFO(" clientsettemp_for_type all ", argv(1), " ", argv(2)); - } - else if(argv(0) != "//") - { - LOG_INFO("Found ^1UNSUPPORTED^7 set command in .cfg file; put this line in mapinfo instead:"); - LOG_INFO(" clientsettemp_for_type all ", argv(0), " ", argv(1)); - } - } - fclose(fd); - } - } - - WeaponStats_Init(); - - Nagger_Init(); - - // set up information replies for clients and server to use - maplist_reply = strzone(getmaplist()); - lsmaps_reply = strzone(getlsmaps()); - monsterlist_reply = strzone(getmonsterlist()); - for(int i = 0; i < 10; ++i) - { - string s = getrecords(i); - if (s) - records_reply[i] = strzone(s); - } - ladder_reply = strzone(getladder()); - rankings_reply = strzone(getrankings()); - - // begin other init - ClientInit_Spawn(); - RandomSeed_Spawn(); - PingPLReport_Spawn(); - - CheatInit(); - - if (!wantrestart) localcmd("\n_sv_hook_gamestart ", GetGametype(), "\n"); - - // fill sv_curl_serverpackages from .serverpackage files - if (autocvar_sv_curl_serverpackages_auto) - { - string s = "csprogs-" WATERMARK ".txt"; - // remove automatically managed files from the list to prevent duplicates - for (int i = 0, n = tokenize_console(cvar_string("sv_curl_serverpackages")); i < n; ++i) - { - string pkg = argv(i); - if (startsWith(pkg, "csprogs-")) continue; - if (endsWith(pkg, "-serverpackage.txt")) continue; - if (endsWith(pkg, ".serverpackage")) continue; // OLD legacy - s = cons(s, pkg); - } - // add automatically managed files to the list - #define X(match) MACRO_BEGIN \ - int fd = search_begin(match, true, false); \ - if (fd >= 0) \ - { \ - for (int i = 0, j = search_getsize(fd); i < j; ++i) \ - { \ - s = cons(s, search_getfilename(fd, i)); \ - } \ - search_end(fd); \ - } \ - MACRO_END - X("*-serverpackage.txt"); - X("*.serverpackage"); - #undef X - cvar_set("sv_curl_serverpackages", s); - } - - // MOD AUTHORS: change this, and possibly remove a few of the blocks below to ignore certain changes - modname = "Xonotic"; - // physics/balance/config changes that count as mod - if(cvar_string("g_mod_physics") != cvar_defstring("g_mod_physics")) - modname = cvar_string("g_mod_physics"); - if(cvar_string("g_mod_balance") != cvar_defstring("g_mod_balance") && cvar_string("g_mod_balance") != "Testing") - modname = cvar_string("g_mod_balance"); - if(cvar_string("g_mod_config") != cvar_defstring("g_mod_config")) - modname = cvar_string("g_mod_config"); - // extra mutators that deserve to count as mod - MUTATOR_CALLHOOK(SetModname, modname); - modname = M_ARGV(0, string); - - // save it for later - modname = strzone(modname); - - WinningConditionHelper(this); // set worldstatus - - world_initialized = 1; - __spawnfunc_spawn_all(); -} - -spawnfunc(light) -{ - //makestatic (this); // Who the f___ did that? - delete(this); -} - -string GetGametype() -{ - return MapInfo_Type_ToString(MapInfo_LoadedGametype); -} - -string GetMapname() -{ - return mapname; -} - -float Map_Count, Map_Current; -string Map_Current_Name; - -// NOTE: this now expects the map list to be already tokenized and the count in Map_Count -int GetMaplistPosition() -{ - string map = GetMapname(); - int idx = autocvar_g_maplist_index; - - if(idx >= 0) - { - if(idx < Map_Count) - { - if(map == argv(idx)) - { - return idx; - } - } - } - - for(int pos = 0; pos < Map_Count; ++pos) - { - if(map == argv(pos)) - return pos; - } - - // resume normal maplist rotation if current map is not in g_maplist - return idx; -} - -bool MapHasRightSize(string map) -{ - int minplayers = max(0, floor(autocvar_minplayers)); - if (teamplay) - minplayers = max(0, floor(autocvar_minplayers_per_team) * AvailableTeams()); - if (autocvar_g_maplist_check_waypoints - && (currentbots || autocvar_bot_number || player_count < minplayers)) - { - string checkwp_msg = strcat("checkwp ", map); - if(!fexists(strcat("maps/", map, ".waypoints"))) - { - LOG_TRACE(checkwp_msg, ": no waypoints"); - return false; - } - LOG_TRACE(checkwp_msg, ": has waypoints"); - } - - if(autocvar_g_maplist_ignore_sizes) - return true; - - // open map size restriction file - string opensize_msg = strcat("opensize ", map); - float fh = fopen(strcat("maps/", map, ".sizes"), FILE_READ); - int player_limit = ((autocvar_g_maplist_sizes_count_maxplayers) ? GetPlayerLimit() : 0); - int pcount = ((player_limit > 0) ? min(player_count, player_limit) : player_count); // bind it to the player limit so that forced spectators don't influence the limits - if(!autocvar_g_maplist_sizes_count_bots) - pcount -= currentbots; - if(fh >= 0) - { - opensize_msg = strcat(opensize_msg, ": ok, "); - int mapmin = stoi(fgets(fh)); - int mapmax = stoi(fgets(fh)); - fclose(fh); - if(pcount < mapmin) - { - LOG_TRACE(opensize_msg, "not enough"); - return false; - } - if(mapmax && pcount > mapmax) - { - LOG_TRACE(opensize_msg, "too many"); - return false; - } - LOG_TRACE(opensize_msg, "right size"); - return true; - } - LOG_TRACE(opensize_msg, ": not found"); - return true; -} - -string Map_Filename(float position) -{ - return strcat("maps/", argv(position), ".bsp"); -} - -void Map_MarkAsRecent(string m) -{ - cvar_set("g_maplist_mostrecent", strwords(cons(m, autocvar_g_maplist_mostrecent), max(0, autocvar_g_maplist_mostrecent_count))); -} - -float Map_IsRecent(string m) -{ - return strhasword(autocvar_g_maplist_mostrecent, m); -} - -float Map_Check(float position, float pass) -{ - string filename; - string map_next; - map_next = argv(position); - if(pass <= 1) - { - if(Map_IsRecent(map_next)) - return 0; - } - filename = Map_Filename(position); - if(MapInfo_CheckMap(map_next)) - { - if(pass == 2) - return 1; - if(MapHasRightSize(map_next)) - return 1; - return 0; - } - else - LOG_DEBUG( "Couldn't select '", filename, "'..." ); - - return 0; -} - -void Map_Goto_SetStr(string nextmapname) -{ - if(getmapname_stored != "") - strunzone(getmapname_stored); - if(nextmapname == "") - getmapname_stored = ""; - else - getmapname_stored = strzone(nextmapname); -} - -void Map_Goto_SetFloat(float position) -{ - cvar_set("g_maplist_index", ftos(position)); - Map_Goto_SetStr(argv(position)); -} - -void Map_Goto(float reinit) -{ - MapInfo_LoadMap(getmapname_stored, reinit); -} - -// return codes of map selectors: -// -1 = temporary failure (that is, try some method that is guaranteed to succeed) -// -2 = permanent failure -float MaplistMethod_Iterate() // usual method -{ - float pass, i; - - LOG_TRACE("Trying MaplistMethod_Iterate"); - - for(pass = 1; pass <= 2; ++pass) - { - for(i = 1; i < Map_Count; ++i) - { - float mapindex; - mapindex = (i + Map_Current) % Map_Count; - if(Map_Check(mapindex, pass)) - return mapindex; - } - } - return -1; -} - -float MaplistMethod_Repeat() // fallback method -{ - LOG_TRACE("Trying MaplistMethod_Repeat"); - - if(Map_Check(Map_Current, 2)) - return Map_Current; - return -2; -} - -float MaplistMethod_Random() // random map selection -{ - float i, imax; - - LOG_TRACE("Trying MaplistMethod_Random"); - - imax = 42; - - for(i = 0; i <= imax; ++i) - { - float mapindex; - mapindex = (Map_Current + floor(random() * (Map_Count - 1) + 1)) % Map_Count; // any OTHER map - if(Map_Check(mapindex, 1)) - return mapindex; - } - return -1; -} - -float MaplistMethod_Shuffle(float exponent) // more clever shuffling -// the exponent sets a bias on the map selection: -// the higher the exponent, the less likely "shortly repeated" same maps are -{ - float i, j, imax, insertpos; - - LOG_TRACE("Trying MaplistMethod_Shuffle"); - - imax = 42; - - for(i = 0; i <= imax; ++i) - { - string newlist; - - // now reinsert this at another position - insertpos = (random() ** (1 / exponent)); // ]0, 1] - insertpos = insertpos * (Map_Count - 1); // ]0, Map_Count - 1] - insertpos = ceil(insertpos) + 1; // {2, 3, 4, ..., Map_Count} - LOG_TRACE("SHUFFLE: insert pos = ", ftos(insertpos)); - - // insert the current map there - newlist = ""; - for(j = 1; j < insertpos; ++j) // i == 1: no loop, will be inserted as first; however, i == 1 has been excluded above - newlist = strcat(newlist, " ", argv(j)); - newlist = strcat(newlist, " ", argv(0)); // now insert the just selected map - for(j = insertpos; j < Map_Count; ++j) // i == Map_Count: no loop, has just been inserted as last - newlist = strcat(newlist, " ", argv(j)); - newlist = substring(newlist, 1, strlen(newlist) - 1); - cvar_set("g_maplist", newlist); - Map_Count = tokenizebyseparator(autocvar_g_maplist, " "); - - // NOTE: the selected map has just been inserted at (insertpos-1)th position - Map_Current = insertpos - 1; // this is not really valid, but this way the fallback has a chance of working - if(Map_Check(Map_Current, 1)) - return Map_Current; - } - return -1; -} - -void Maplist_Init() -{ - float i = Map_Count = 0; - if(autocvar_g_maplist != "") - { - Map_Count = tokenizebyseparator(autocvar_g_maplist, " "); - for (i = 0; i < Map_Count; ++i) - { - if (Map_Check(i, 2)) - break; - } - } - - if (i == Map_Count) - { - bprint( "Maplist contains no usable maps! Resetting it to default map list.\n" ); - cvar_set("g_maplist", MapInfo_ListAllAllowedMaps(MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags() | MAPINFO_FLAG_NOAUTOMAPLIST)); - if(autocvar_g_maplist_shuffle) - ShuffleMaplist(); - if(!server_is_dedicated) - localcmd("\nmenu_cmd sync\n"); - Map_Count = tokenizebyseparator(autocvar_g_maplist, " "); - } - if(Map_Count == 0) - error("empty maplist, cannot select a new map"); - Map_Current = bound(0, GetMaplistPosition(), Map_Count - 1); - - strcpy(Map_Current_Name, argv(Map_Current)); // will be automatically freed on exit thanks to DP - // this may or may not be correct, but who cares, in the worst case a map - // isn't chosen in the first pass that should have been -} - -string GetNextMap() -{ - Maplist_Init(); - float nextMap = -1; - - if(nextMap == -1) - if(autocvar_g_maplist_shuffle > 0) - nextMap = MaplistMethod_Shuffle(autocvar_g_maplist_shuffle + 1); - - if(nextMap == -1) - if(autocvar_g_maplist_selectrandom) - nextMap = MaplistMethod_Random(); - - if(nextMap == -1) - nextMap = MaplistMethod_Iterate(); - - if(nextMap == -1) - nextMap = MaplistMethod_Repeat(); - - if(nextMap >= 0) - { - Map_Goto_SetFloat(nextMap); - return getmapname_stored; - } - - return ""; -} - -float DoNextMapOverride(float reinit) -{ - if(autocvar_g_campaign) - { - CampaignPostIntermission(); - alreadychangedlevel = true; - return true; - } - if(autocvar_quit_when_empty) - { - if(player_count <= currentbots) - { - localcmd("quit\n"); - alreadychangedlevel = true; - return true; - } - } - if(autocvar_quit_and_redirect != "") - { - redirection_target = strzone(autocvar_quit_and_redirect); - alreadychangedlevel = true; - return true; - } - if (!reinit && autocvar_samelevel) // if samelevel is set, stay on same level - { - localcmd("restart\n"); - alreadychangedlevel = true; - return true; - } - if(autocvar_nextmap != "") - { - string m; - m = GameTypeVote_MapInfo_FixName(autocvar_nextmap); - cvar_set("nextmap",m); - - if(!m || gametypevote) - return false; - if(autocvar_sv_vote_gametype) - { - Map_Goto_SetStr(m); - return false; - } - - if(MapInfo_CheckMap(m)) - { - Map_Goto_SetStr(m); - Map_Goto(reinit); - alreadychangedlevel = true; - return true; - } - } - if(!reinit && autocvar_lastlevel) - { - cvar_settemp_restore(); - localcmd("set lastlevel 0\ntogglemenu 1\n"); - alreadychangedlevel = true; - return true; - } - return false; -} - -void GotoNextMap(float reinit) -{ - //string nextmap; - //float n, nummaps; - //string s; - if (alreadychangedlevel) - return; - alreadychangedlevel = true; - - string nextMap = GetNextMap(); - if(nextMap == "") - error("Everything is broken - cannot find a next map. Please report this to the developers."); - Map_Goto(reinit); -} - - -/* -============ -IntermissionThink - -When the player presses attack or jump, change to the next level -============ -*/ -.float autoscreenshot; -void IntermissionThink(entity this) -{ - FixIntermissionClient(this); - - float server_screenshot = (autocvar_sv_autoscreenshot && CS(this).cvar_cl_autoscreenshot); - float client_screenshot = (CS(this).cvar_cl_autoscreenshot == 2); - - if( (server_screenshot || client_screenshot) - && ((this.autoscreenshot > 0) && (time > this.autoscreenshot)) ) - { - this.autoscreenshot = -1; - if(IS_REAL_CLIENT(this)) { stuffcmd(this, sprintf("\nscreenshot screenshots/autoscreenshot/%s-%s.jpg; echo \"^5A screenshot has been taken at request of the server.\"\n", GetMapname(), strftime(false, "%s"))); } - return; - } - - if (time < intermission_exittime) - return; - - if(!mapvote_initialized) - if (time < intermission_exittime + 10 && !(PHYS_INPUT_BUTTON_ATCK(this) || PHYS_INPUT_BUTTON_JUMP(this) || PHYS_INPUT_BUTTON_ATCK2(this) || PHYS_INPUT_BUTTON_HOOK(this) || PHYS_INPUT_BUTTON_USE(this))) - return; - - MapVote_Start(); -} - -/* -=============================================================================== - -RULES - -=============================================================================== -*/ - -void DumpStats(float final) -{ - float file; - string s; - float to_console; - float to_eventlog; - float to_file; - float i; - - to_console = autocvar_sv_logscores_console; - to_eventlog = autocvar_sv_eventlog; - to_file = autocvar_sv_logscores_file; - - if(!final) - { - to_console = true; // always print printstats replies - to_eventlog = false; // but never print them to the event log - } - - if(to_eventlog) - if(autocvar_sv_eventlog_console) - to_console = false; // otherwise we get the output twice - - if(final) - s = ":scores:"; - else - s = ":status:"; - s = strcat(s, GetGametype(), "_", GetMapname(), ":", ftos(rint(time))); - - if(to_console) - LOG_INFO(s); - if(to_eventlog) - GameLogEcho(s); - - file = -1; - if(to_file) - { - file = fopen(autocvar_sv_logscores_filename, FILE_APPEND); - if(file == -1) - to_file = false; - else - fputs(file, strcat(s, "\n")); - } - - s = strcat(":labels:player:", GetPlayerScoreString(NULL, 0)); - if(to_console) - LOG_INFO(s); - if(to_eventlog) - GameLogEcho(s); - if(to_file) - fputs(file, strcat(s, "\n")); - - FOREACH_CLIENT(IS_REAL_CLIENT(it) || (IS_BOT_CLIENT(it) && autocvar_sv_logscores_bots), { - s = strcat(":player:see-labels:", GetPlayerScoreString(it, 0), ":"); - s = strcat(s, ftos(rint(time - CS(it).jointime)), ":"); - if(IS_PLAYER(it) || MUTATOR_CALLHOOK(GetPlayerStatus, it)) - s = strcat(s, ftos(it.team), ":"); - else - s = strcat(s, "spectator:"); - - if(to_console) - LOG_INFO(s, playername(it, false)); - if(to_eventlog) - GameLogEcho(strcat(s, ftos(it.playerid), ":", playername(it, false))); - if(to_file) - fputs(file, strcat(s, playername(it, false), "\n")); - }); - - if(teamplay) - { - s = strcat(":labels:teamscores:", GetTeamScoreString(0, 0)); - if(to_console) - LOG_INFO(s); - if(to_eventlog) - GameLogEcho(s); - if(to_file) - fputs(file, strcat(s, "\n")); - - for(i = 1; i < 16; ++i) - { - s = strcat(":teamscores:see-labels:", GetTeamScoreString(i, 0)); - s = strcat(s, ":", ftos(i)); - if(to_console) - LOG_INFO(s); - if(to_eventlog) - GameLogEcho(s); - if(to_file) - fputs(file, strcat(s, "\n")); - } - } - - if(to_console) - LOG_INFO(":end"); - if(to_eventlog) - GameLogEcho(":end"); - if(to_file) - { - fputs(file, ":end\n"); - fclose(file); - } -} - -void FixIntermissionClient(entity e) -{ - if(!e.autoscreenshot) // initial call - { - e.autoscreenshot = time + 0.8; // used for autoscreenshot - SetResourceExplicit(e, RES_HEALTH, -2342); - // first intermission phase; voting phase has positive health (used to decide whether to send SVC_FINALE or not) - for (int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) - { - .entity weaponentity = weaponentities[slot]; - if(e.(weaponentity)) - { - e.(weaponentity).effects = EF_NODRAW; - if (e.(weaponentity).weaponchild) - e.(weaponentity).weaponchild.effects = EF_NODRAW; - } - } - if(IS_REAL_CLIENT(e)) - { - stuffcmd(e, "\nscr_printspeed 1000000\n"); - RandomSelection_Init(); - FOREACH_WORD(autocvar_sv_intermission_cdtrack, true, { - RandomSelection_AddString(it, 1, 1); - }); - if (RandomSelection_chosen_string != "") - { - stuffcmd(e, sprintf("\ncd loop %s\n", RandomSelection_chosen_string)); - } - msg_entity = e; - WriteByte(MSG_ONE, SVC_INTERMISSION); - } - } -} - -/* -go to the next level for deathmatch -only called if a time or frag limit has expired -*/ -void NextLevel() -{ - game_stopped = true; - intermission_running = 1; // game over - - // enforce a wait time before allowing changelevel - if(player_count > 0) - intermission_exittime = time + autocvar_sv_mapchange_delay; - else - intermission_exittime = -1; - - /* - WriteByte (MSG_ALL, SVC_CDTRACK); - WriteByte (MSG_ALL, 3); - WriteByte (MSG_ALL, 3); - // done in FixIntermission - */ - - //pos = FindIntermission (); - - VoteReset(); - - DumpStats(true); - - // send statistics - PlayerStats_GameReport(true); - WeaponStats_Shutdown(); - - Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_Null); // kill all centerprints now - - if(autocvar_sv_eventlog) - GameLogEcho(":gameover"); - - GameLogClose(); - - FOREACH_CLIENT(IS_PLAYER(it), { - FixIntermissionClient(it); - if(it.winning) - bprint(playername(it, false), " ^7wins.\n"); - }); - - target_music_kill(); - - if(autocvar_g_campaign) - CampaignPreIntermission(); - - MUTATOR_CALLHOOK(MatchEnd); - - localcmd("\nsv_hook_gameend\n"); -} - - -float InitiateSuddenDeath() -{ - // Check first whether normal overtimes could be added before initiating suddendeath mode - // - for this timelimit_overtime needs to be >0 of course - // - also check the winning condition calculated in the previous frame and only add normal overtime - // again, if at the point at which timelimit would be extended again, still no winner was found - if (!autocvar_g_campaign && checkrules_overtimesadded >= 0 - && (checkrules_overtimesadded < autocvar_timelimit_overtimes || autocvar_timelimit_overtimes < 0) - && autocvar_timelimit_overtime && !(g_race && !g_race_qualifying)) - { - return 1; // need to call InitiateOvertime later - } - else - { - if(!checkrules_suddendeathend) - { - if(autocvar_g_campaign) - checkrules_suddendeathend = time; // no suddendeath in campaign - else - checkrules_suddendeathend = time + 60 * autocvar_timelimit_suddendeath; - if(g_race && !g_race_qualifying) - race_StartCompleting(); - } - return 0; - } -} - -void InitiateOvertime() // ONLY call this if InitiateSuddenDeath returned true -{ - ++checkrules_overtimesadded; - //add one more overtime by simply extending the timelimit - cvar_set("timelimit", ftos(autocvar_timelimit + autocvar_timelimit_overtime)); - Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_OVERTIME_TIME, autocvar_timelimit_overtime * 60); -} - -float GetWinningCode(float fraglimitreached, float equality) -{ - if(autocvar_g_campaign == 1) - { - if(fraglimitreached) - return WINNING_YES; - else - return WINNING_NO; - } - else - { - if(equality) - { - if(fraglimitreached) - return WINNING_STARTSUDDENDEATHOVERTIME; - else - return WINNING_NEVER; - } - else - { - if(fraglimitreached) - return WINNING_YES; - else - return WINNING_NO; - } - } -} - -// set the .winning flag for exactly those players with a given field value -void SetWinners(.float field, float value) -{ - FOREACH_CLIENT(IS_PLAYER(it), { it.winning = (it.(field) == value); }); -} - -// set the .winning flag for those players with a given field value -void AddWinners(.float field, float value) -{ - FOREACH_CLIENT(IS_PLAYER(it), { - if(it.(field) == value) - it.winning = 1; - }); -} - -// clear the .winning flags -void ClearWinners() -{ - FOREACH_CLIENT(IS_PLAYER(it), { it.winning = 0; }); -} - -void ShuffleMaplist() -{ - cvar_set("g_maplist", shufflewords(autocvar_g_maplist)); -} - -int fragsleft_last; -float WinningCondition_Scores(float limit, float leadlimit) -{ - // TODO make everything use THIS winning condition (except LMS) - WinningConditionHelper(NULL); - - if(teamplay) - { - for (int i = 1; i < 5; ++i) - { - Team_SetTeamScore(Team_GetTeamFromIndex(i), - TeamScore_GetCompareValue(Team_IndexToTeam(i))); - } - } - - ClearWinners(); - if(WinningConditionHelper_winner) - WinningConditionHelper_winner.winning = 1; - if(WinningConditionHelper_winnerteam >= 0) - SetWinners(team, WinningConditionHelper_winnerteam); - - if(WinningConditionHelper_lowerisbetter) - { - WinningConditionHelper_topscore = -WinningConditionHelper_topscore; - WinningConditionHelper_secondscore = -WinningConditionHelper_secondscore; - limit = -limit; - } - - if(WinningConditionHelper_zeroisworst) - leadlimit = 0; // not supported in this mode - - if(MUTATOR_CALLHOOK(Scores_CountFragsRemaining)) - { - float fragsleft; - if (checkrules_suddendeathend && time >= checkrules_suddendeathend) - { - fragsleft = 1; - } - else - { - fragsleft = FLOAT_MAX; - float leadingfragsleft = FLOAT_MAX; - if (limit) - fragsleft = limit - WinningConditionHelper_topscore; - if (leadlimit) - leadingfragsleft = WinningConditionHelper_secondscore + leadlimit - WinningConditionHelper_topscore; - - if (limit && leadlimit && autocvar_leadlimit_and_fraglimit) - fragsleft = max(fragsleft, leadingfragsleft); - else - fragsleft = min(fragsleft, leadingfragsleft); - } - - if (fragsleft_last != fragsleft) // do not announce same remaining frags multiple times - { - if (fragsleft == 1) - Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_REMAINING_FRAG_1); - else if (fragsleft == 2) - Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_REMAINING_FRAG_2); - else if (fragsleft == 3) - Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_REMAINING_FRAG_3); - - fragsleft_last = fragsleft; - } - } - - bool fraglimit_reached = (limit && WinningConditionHelper_topscore >= limit); - bool leadlimit_reached = (leadlimit && WinningConditionHelper_topscore - WinningConditionHelper_secondscore >= leadlimit); - - bool limit_reached; - // only respect leadlimit_and_fraglimit when both limits are set or the game will never end - if (limit && leadlimit && autocvar_leadlimit_and_fraglimit) - limit_reached = (fraglimit_reached && leadlimit_reached); - else - limit_reached = (fraglimit_reached || leadlimit_reached); - - return GetWinningCode( - WinningConditionHelper_topscore && limit_reached, - WinningConditionHelper_equality - ); -} - -float WinningCondition_RanOutOfSpawns() -{ - if(have_team_spawns <= 0) - return WINNING_NO; - - if(!autocvar_g_spawn_useallspawns) - return WINNING_NO; - - if(!some_spawn_has_been_used) - return WINNING_NO; - - for (int i = 1; i < 5; ++i) - { - Team_SetTeamScore(Team_GetTeamFromIndex(i), 0); - } - - FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), - { - if (Team_IsValidTeam(it.team)) - { - Team_SetTeamScore(Team_GetTeam(it.team), 1); - } - }); - - IL_EACH(g_spawnpoints, true, - { - if (Team_IsValidTeam(it.team)) - { - Team_SetTeamScore(Team_GetTeam(it.team), 1); - } - }); - - ClearWinners(); - float team1_score = Team_GetTeamScore(Team_GetTeamFromIndex(1)); - float team2_score = Team_GetTeamScore(Team_GetTeamFromIndex(2)); - float team3_score = Team_GetTeamScore(Team_GetTeamFromIndex(3)); - float team4_score = Team_GetTeamScore(Team_GetTeamFromIndex(4)); - if(team1_score + team2_score + team3_score + team4_score == 0) - { - checkrules_equality = true; - return WINNING_YES; - } - else if(team1_score + team2_score + team3_score + team4_score == 1) - { - float t, i; - if(team1_score) - t = 1; - else if(team2_score) - t = 2; - else if(team3_score) - t = 3; - else // if(team4_score) - t = 4; - entity balance = TeamBalance_CheckAllowedTeams(NULL); - for(i = 0; i < MAX_TEAMSCORE; ++i) - { - for (int j = 1; j <= NUM_TEAMS; ++j) - { - if (t == j) - { - continue; - } - if (!TeamBalance_IsTeamAllowed(balance, j)) - { - continue; - } - TeamScore_AddToTeam(Team_IndexToTeam(j), i, -1000); - } - } - - AddWinners(team, t); - return WINNING_YES; - } - else - return WINNING_NO; -} - -/* -============ -CheckRules_World - -Exit deathmatch games upon conditions -============ -*/ -void CheckRules_World() -{ - VoteThink(); - MapVote_Think(); - - SetDefaultAlpha(); - - if (intermission_running) // someone else quit the game already - { - if(player_count == 0) // Nobody there? Then let's go to the next map - MapVote_Start(); - // this will actually check the player count in the next frame - // again, but this shouldn't hurt - return; - } - - float timelimit = autocvar_timelimit * 60; - float fraglimit = autocvar_fraglimit; - float leadlimit = autocvar_leadlimit; - if (leadlimit < 0) leadlimit = 0; - - if(warmup_stage || time <= game_starttime) // NOTE: this is <= to prevent problems in the very tic where the game starts - { - if(timelimit > 0) - timelimit = 0; // timelimit is not made for warmup - if(fraglimit > 0) - fraglimit = 0; // no fraglimit for now - leadlimit = 0; // no leadlimit for now - } - - if(timelimit > 0) - { - timelimit += game_starttime; - } - else if (timelimit < 0) - { - // endmatch - NextLevel(); - return; - } - - float wantovertime; - wantovertime = 0; - - if(checkrules_suddendeathend) - { - if(!checkrules_suddendeathwarning) - { - checkrules_suddendeathwarning = true; - if(g_race && !g_race_qualifying) - Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_RACE_FINISHLAP); - else - Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_OVERTIME_FRAG); - } - } - else - { - if (timelimit && time >= timelimit) - { - if(g_race && (g_race_qualifying == 2) && timelimit > 0) - { - float totalplayers; - float playerswithlaps; - float readyplayers; - totalplayers = playerswithlaps = readyplayers = 0; - FOREACH_CLIENT(IS_PLAYER(it), { - ++totalplayers; - if(GameRules_scoring_add(it, RACE_FASTEST, 0)) - ++playerswithlaps; - if(it.ready) - ++readyplayers; - }); - - // at least 2 of the players have completed a lap: start the RACE - // otherwise, the players should end the qualifying on their own - if(readyplayers || playerswithlaps >= 2) - { - checkrules_suddendeathend = 0; - ReadyRestart(); // go to race - return; - } - else - wantovertime |= InitiateSuddenDeath(); - } - else - wantovertime |= InitiateSuddenDeath(); - } - } - - if (checkrules_suddendeathend && time >= checkrules_suddendeathend) - { - NextLevel(); - return; - } - - int checkrules_status = WinningCondition_RanOutOfSpawns(); - if(checkrules_status == WINNING_YES) - bprint("Hey! Someone ran out of spawns!\n"); - else if(MUTATOR_CALLHOOK(CheckRules_World, checkrules_status, timelimit, fraglimit)) - checkrules_status = M_ARGV(0, float); - else - checkrules_status = WinningCondition_Scores(fraglimit, leadlimit); - - if(checkrules_status == WINNING_STARTSUDDENDEATHOVERTIME) - { - checkrules_status = WINNING_NEVER; - checkrules_overtimesadded = -1; - wantovertime |= InitiateSuddenDeath(); - } - - if(checkrules_status == WINNING_NEVER) - // equality cases! Nobody wins if the overtime ends in a draw. - ClearWinners(); - - if(wantovertime) - { - if(checkrules_status == WINNING_NEVER) - InitiateOvertime(); - else - checkrules_status = WINNING_YES; - } - - if(checkrules_suddendeathend) - if(checkrules_status != WINNING_NEVER || time >= checkrules_suddendeathend) - checkrules_status = WINNING_YES; - - if(checkrules_status == WINNING_YES) - { - //print("WINNING\n"); - NextLevel(); - } -} - -string GotoMap(string m) -{ - m = GameTypeVote_MapInfo_FixName(m); - if (!m) - return "The map you suggested is not available on this server."; - if (!autocvar_sv_vote_gametype) - if(!MapInfo_CheckMap(m)) - return "The map you suggested does not support the current game mode."; - cvar_set("nextmap", m); - cvar_set("timelimit", "-1"); - if(mapvote_initialized || alreadychangedlevel) - { - if(DoNextMapOverride(0)) - return "Map switch initiated."; - else - return "Hm... no. For some reason I like THIS map more."; - } - else - return "Map switch will happen after scoreboard."; -} - -bool autocvar_sv_gameplayfix_multiplethinksperframe = true; -void RunThink(entity this) -{ - // don't let things stay in the past. - // it is possible to start that way by a trigger with a local time. - if(this.nextthink <= 0 || this.nextthink > time + frametime) - return; - - float oldtime = time; // do we need to save this? - - for (int iterations = 0; iterations < 128 && !wasfreed(this); iterations++) - { - time = max(oldtime, this.nextthink); - this.nextthink = 0; - - if(getthink(this)) - getthink(this)(this); - // mods often set nextthink to time to cause a think every frame, - // we don't want to loop in that case, so exit if the new nextthink is - // <= the time the qc was told, also exit if it is past the end of the - // frame - if(this.nextthink <= time || this.nextthink > oldtime + frametime || !autocvar_sv_gameplayfix_multiplethinksperframe) - break; - } - - time = oldtime; -} - -bool autocvar_sv_freezenonclients; -bool autocvar_sv_gameplayfix_delayprojectiles = false; -void Physics_Frame() -{ - if(autocvar_sv_freezenonclients) - return; - - IL_EACH(g_moveables, true, - { - if(IS_CLIENT(it) || it.classname == "" || it.move_movetype == MOVETYPE_PHYSICS) - continue; - - //set_movetype(it, it.move_movetype); - // inline the set_movetype function, since this is called a lot - it.movetype = (it.move_qcphysics) ? MOVETYPE_QCENTITY : it.move_movetype; - - if(it.move_qcphysics && it.move_movetype != MOVETYPE_NONE) - Movetype_Physics_NoMatchTicrate(it, PHYS_INPUT_TIMELENGTH, false); - - if(it.movetype >= MOVETYPE_USER_FIRST && it.movetype <= MOVETYPE_USER_LAST) // these cases have no think handling - { - if(it.move_movetype == MOVETYPE_PUSH || it.move_movetype == MOVETYPE_FAKEPUSH) - continue; // these movetypes have no regular think function - // handle thinking here - if (getthink(it) && it.nextthink > 0 && it.nextthink <= time + frametime) - RunThink(it); - } - }); - - if(autocvar_sv_gameplayfix_delayprojectiles >= 0) - return; - - IL_EACH(g_moveables, it.move_qcphysics, - { - if(IS_CLIENT(it) || it.classname == "" || it.move_movetype == MOVETYPE_NONE) - continue; - Movetype_Physics_NoMatchTicrate(it, PHYS_INPUT_TIMELENGTH, false); - }); -} - -void systems_update(); -void EndFrame() -{ - anticheat_endframe(); - - Physics_Frame(); - - FOREACH_CLIENT(IS_REAL_CLIENT(it), { - entity e = IS_SPEC(it) ? it.enemy : it; - if (e.typehitsound) { - STAT(TYPEHIT_TIME, it) = time; - } else if (e.killsound) { - STAT(KILL_TIME, it) = time; - } else if (e.damage_dealt) { - STAT(HIT_TIME, it) = time; - STAT(DAMAGE_DEALT_TOTAL, it) += ceil(e.damage_dealt); - } - }); - // add 1 frametime because after this, engine SV_Physics - // increases time by a frametime and then networks the frame - // add another frametime because client shows everything with - // 1 frame of lag (cl_nolerp 0). The last +1 however should not be - // needed! - float altime = time + frametime * (1 + autocvar_g_antilag_nudge); - FOREACH_CLIENT(true, { - it.typehitsound = false; - it.damage_dealt = 0; - it.killsound = false; - antilag_record(it, CS(it), altime); - }); - IL_EACH(g_monsters, true, - { - antilag_record(it, it, altime); - }); - IL_EACH(g_projectiles, it.classname == "nade", - { - antilag_record(it, it, altime); - }); - systems_update(); - IL_ENDFRAME(); -} - - -/* - * RedirectionThink: - * returns true if redirecting - */ -float redirection_timeout; -float redirection_nextthink; -float RedirectionThink() -{ - float clients_found; - - if(redirection_target == "") - return false; - - if(!redirection_timeout) - { - cvar_set("sv_public", "-2"); - redirection_timeout = time + 0.6; // this will only try twice... should be able to keep more clients - if(redirection_target == "self") - bprint("^3SERVER NOTICE:^7 restarting the server\n"); - else - bprint("^3SERVER NOTICE:^7 redirecting everyone to ", redirection_target, "\n"); - } - - if(time < redirection_nextthink) - return true; - - redirection_nextthink = time + 1; - - clients_found = 0; - FOREACH_CLIENT(IS_REAL_CLIENT(it), { - // TODO add timer - LOG_INFO("Redirecting: sending connect command to ", it.netname); - if(redirection_target == "self") - stuffcmd(it, "\ndisconnect; defer ", ftos(autocvar_quit_and_redirect_timer), " reconnect\n"); - else - stuffcmd(it, strcat("\ndisconnect; defer ", ftos(autocvar_quit_and_redirect_timer), " \"connect ", redirection_target, "\"\n")); - ++clients_found; - }); - - LOG_INFO("Redirecting: ", ftos(clients_found), " clients left."); - - if(time > redirection_timeout || clients_found == 0) - localcmd("\nwait; wait; wait; quit\n"); - - return true; -} - -void RestoreGame() -{ - // Loaded from a save game - // some things then break, so let's work around them... - - // Progs DB (capture records) - ServerProgsDB = db_load(strcat("server.db", autocvar_sessionid)); - - // Mapinfo - MapInfo_Shutdown(); - MapInfo_Enumerate(); - MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags(), 1); - WeaponStats_Init(); - - TargetMusic_RestoreGame(); -} - -void Shutdown() -{ - game_stopped = 2; - - if(world_initialized > 0) - { - world_initialized = 0; - - // if a timeout is active, reset the slowmo value to normal - if(timeout_status == TIMEOUT_ACTIVE) - cvar_set("slowmo", ftos(orig_slowmo)); - - LOG_TRACE("Saving persistent data..."); - Ban_SaveBans(); - - // playerstats with unfinished match - PlayerStats_GameReport(false); - - if(!cheatcount_total) - { - if(autocvar_sv_db_saveasdump) - db_dump(ServerProgsDB, strcat("server.db", autocvar_sessionid)); - else - db_save(ServerProgsDB, strcat("server.db", autocvar_sessionid)); - } - if(autocvar_developer > 0) - { - if(autocvar_sv_db_saveasdump) - db_dump(TemporaryDB, "server-temp.db"); - else - db_save(TemporaryDB, "server-temp.db"); - } - CheatShutdown(); // must be after cheatcount check - db_close(ServerProgsDB); - db_close(TemporaryDB); - LOG_TRACE("Saving persistent data... done!"); - // tell the bot system the game is ending now - bot_endgame(); - - WeaponStats_Shutdown(); - MapInfo_Shutdown(); - } - else if(world_initialized == 0) - { - LOG_INFO("NOTE: crashed before even initializing the world, not saving persistent data"); - } - else - { - __init_dedicated_server_shutdown(); - } -} diff --git a/qcsrc/server/g_world.qh b/qcsrc/server/g_world.qh deleted file mode 100644 index 3bbaad682..000000000 --- a/qcsrc/server/g_world.qh +++ /dev/null @@ -1,65 +0,0 @@ -#pragma once - -float checkrules_equality; -float checkrules_suddendeathwarning; -float checkrules_suddendeathend; -float checkrules_overtimesadded; //how many overtimes have been already added - -// flag set on worldspawn so that the code knows if it is dedicated or not -bool server_is_dedicated; - -string cvar_changes; -string cvar_purechanges; -float cvar_purechanges_count; - -string modname; - -string gamemode_name; - -string clientstuff; - -string matchid; - -.string fog; - -float intermission_running; -float intermission_exittime; -float alreadychangedlevel; - -string cache_mutatormsg; -string cache_lastmutatormsg; - -float default_player_alpha; -float default_weapon_alpha; - -// database -float ServerProgsDB; -float TemporaryDB; - -.float winning; -const int WINNING_NO = 0; // no winner, but time limits may terminate the game -const int WINNING_YES = 1; // winner found -const int WINNING_NEVER = 2; // no winner, enter overtime if time limit is reached -const int WINNING_STARTSUDDENDEATHOVERTIME = 3; // no winner, enter suddendeath overtime NOW - -float WinningCondition_Scores(float limit, float leadlimit); -void SetWinners(.float field, float value); -void IntermissionThink(entity this); -void GotoNextMap(float reinit); -void ReadyRestart(); - -string GetGametype(); - -void DumpStats(float final); -float Map_IsRecent(string m); -string GetNextMap(); -void ShuffleMaplist(); -void Map_Goto_SetStr(string nextmapname); -void Map_Goto(float reinit); -void Map_MarkAsRecent(string m); -float DoNextMapOverride(float reinit); -void CheckRules_World(); -float RedirectionThink(); - -IntrusiveList g_moveables; -STATIC_INIT(g_moveables) { g_moveables = IL_NEW(); } diff --git a/qcsrc/server/hook.qc b/qcsrc/server/hook.qc new file mode 100644 index 000000000..6042a48e1 --- /dev/null +++ b/qcsrc/server/hook.qc @@ -0,0 +1,435 @@ +#include "hook.qh" + +#include +#include +#include +#include +#include +#include +#include "weapons/common.qh" +#include "weapons/csqcprojectile.qh" +#include "weapons/weaponsystem.qh" +#include "weapons/selection.qh" +#include "weapons/tracing.qh" +#include "player.qh" +#include "command/common.qh" +#include "command/vote.qh" +#include "round_handler.qh" +#include "../common/state.qh" +#include "../common/physics/player.qh" +#include "../common/vehicles/all.qh" +#include "../common/constants.qh" +#include "../common/util.qh" +#include +#include +#include "../lib/warpzone/common.qh" +#include "../lib/warpzone/server.qh" + +/*============================================ + + Wazat's Xonotic Grappling Hook + + Contact: Wazat1@gmail.com + + +Installation instructions: +-------------------------- + +1. Place hook.c in your gamec source directory with the other source files. + +2. Add this line to the bottom of progs.src: + +gamec/hook.c + +3. Open defs.h and add these lines to the very bottom: + +// Wazat's grappling hook +.entity hook; +void GrapplingHookFrame(); +void RemoveGrapplingHook(entity pl); +void SetGrappleHookBindings(); +// hook impulses +const float GRAPHOOK_FIRE = 20; +const float GRAPHOOK_RELEASE = 21; +// (note: you can change the hook impulse #'s to whatever you please) + +4. Open client.c and add this to the top of PutClientInServer(): + + RemoveGrapplingHook(this); // Wazat's Grappling Hook + +5. Find ClientConnect() (in client.c) and add these lines to the bottom: + + // Wazat's grappling hook + SetGrappleHookBindings(); + +6. Still in client.c, find PlayerPreThink and add this line just above the call to W_WeaponFrame: + + GrapplingHookFrame(); + +7. Build and test the mod. You'll want to bind a key to "+hook" like this: +bind ctrl "+hook" + +And you should be done! + + +============================================*/ + +void RemoveGrapplingHooks(entity pl) +{ + if(pl.move_movetype == MOVETYPE_FLY) + set_movetype(pl, MOVETYPE_WALK); + + for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) + { + .entity weaponentity = weaponentities[slot]; + if(!pl.(weaponentity)) + continue; // continue incase other slots exist? + if(pl.(weaponentity).hook) + delete(pl.(weaponentity).hook); + pl.(weaponentity).hook = NULL; + } + + //pl.disableclientprediction = false; +} + +void RemoveHook(entity this) +{ + entity player = this.realowner; + .entity weaponentity = this.weaponentity_fld; + + if(player.(weaponentity).hook == this) + player.(weaponentity).hook = NULL; + + if(player.move_movetype == MOVETYPE_FLY) + set_movetype(player, MOVETYPE_WALK); + delete(this); +} + +void GrapplingHookReset(entity this) +{ + RemoveHook(this); +} + +void GrapplingHook_Stop(entity this) +{ + Send_Effect(EFFECT_HOOK_IMPACT, this.origin, '0 0 0', 1); + sound (this, CH_SHOTS, SND_HOOK_IMPACT, VOL_BASE, ATTEN_NORM); + + this.state = 1; + setthink(this, GrapplingHookThink); + this.nextthink = time; + settouch(this, func_null); + this.velocity = '0 0 0'; + set_movetype(this, MOVETYPE_NONE); + this.hook_length = -1; +} + +.vector hook_start, hook_end; +bool GrapplingHookSend(entity this, entity to, int sf) +{ + WriteHeader(MSG_ENTITY, ENT_CLIENT_HOOK); + sf = sf & 0x7F; + if(sound_allowed(MSG_BROADCAST, this.realowner)) + sf |= 0x80; + WriteByte(MSG_ENTITY, sf); + if(sf & 1) + { + WriteByte(MSG_ENTITY, etof(this.realowner)); + WriteByte(MSG_ENTITY, weaponslot(this.weaponentity_fld)); + } + if(sf & 2) + { + WriteVector(MSG_ENTITY, this.hook_start); + } + if(sf & 4) + { + WriteVector(MSG_ENTITY, this.hook_end); + } + return true; +} + +int autocvar_g_grappling_hook_tarzan; + +void GrapplingHookThink(entity this) +{ + float spd, dist, minlength, pullspeed, ropestretch, ropeairfriction, rubberforce, newlength, rubberforce_overstretch; + vector dir, org, end, v0, dv, v, myorg, vs; + .entity weaponentity = this.weaponentity_fld; + if(this.realowner.(weaponentity).hook != this) // how did that happen? + { + error("Owner lost the hook!\n"); + return; + } + if(LostMovetypeFollow(this) || game_stopped || (round_handler_IsActive() && !round_handler_IsRoundStarted()) || ((this.aiment.flags & FL_PROJECTILE) && this.aiment.classname != "nade")) + { + RemoveHook(this); + return; + } + if(this.aiment) + WarpZone_RefSys_AddIncrementally(this, this.aiment); + + this.nextthink = time; + + int s = W_GunAlign(this.realowner.(weaponentity), STAT(GUNALIGN, this.realowner)) - 1; + vs = hook_shotorigin[s]; + + makevectors(this.realowner.v_angle); + org = this.realowner.origin + this.realowner.view_ofs + v_forward * vs.x + v_right * -vs.y + v_up * vs.z; + myorg = WarpZone_RefSys_TransformOrigin(this.realowner, this, org); + + if(this.hook_length < 0) + this.hook_length = vlen(myorg - this.origin); + + int tarzan = autocvar_g_grappling_hook_tarzan; + entity pull_entity = this.realowner; + float velocity_multiplier = 1; + MUTATOR_CALLHOOK(GrappleHookThink, this, tarzan, pull_entity, velocity_multiplier); + tarzan = M_ARGV(1, int); + pull_entity = M_ARGV(2, entity); + velocity_multiplier = M_ARGV(3, float); + + if(this.state == 1) + { + pullspeed = autocvar_g_balance_grapplehook_speed_pull;//2000; + // speed the rope is pulled with + + rubberforce = autocvar_g_balance_grapplehook_force_rubber;//2000; + // force the rope will use if it is stretched + + rubberforce_overstretch = autocvar_g_balance_grapplehook_force_rubber_overstretch;//1000; + // force the rope will use if it is stretched + + minlength = autocvar_g_balance_grapplehook_length_min;//100; + // minimal rope length + // if the rope goes below this length, it isn't pulled any more + + ropestretch = autocvar_g_balance_grapplehook_stretch;//400; + // if the rope is stretched by more than this amount, more rope is + // given to you again + + ropeairfriction = autocvar_g_balance_grapplehook_airfriction;//0.2 + // while hanging on the rope, this friction component will help you a + // bit to control the rope + + bool frozen_pulling = (autocvar_g_grappling_hook_tarzan >= 2 && autocvar_g_balance_grapplehook_pull_frozen); + + dir = this.origin - myorg; + dist = vlen(dir); + dir = normalize(dir); + + if(tarzan) + { + v = v0 = WarpZone_RefSys_TransformVelocity(pull_entity, this, pull_entity.velocity); + + // first pull the rope... + if(this.realowner.(weaponentity).hook_state & HOOK_PULLING) + { + newlength = this.hook_length; + newlength = max(newlength - pullspeed * frametime, minlength); + + if(newlength < dist - ropestretch) // overstretched? + { + newlength = dist - ropestretch; + if(v * dir < 0) // only if not already moving in hook direction + v = v + frametime * dir * rubberforce_overstretch; + } + + this.hook_length = newlength; + } + + if(pull_entity.move_movetype == MOVETYPE_FLY) + set_movetype(pull_entity, MOVETYPE_WALK); + + if(this.realowner.(weaponentity).hook_state & HOOK_RELEASING) + { + newlength = dist; + this.hook_length = newlength; + } + else + { + // then pull the player + spd = bound(0, (dist - this.hook_length) / ropestretch, 1); + v = v * (1 - frametime * ropeairfriction); + v = v + frametime * dir * spd * rubberforce; + + dv = ((v - v0) * dir) * dir; + if(tarzan >= 2) + { + if(this.aiment.move_movetype == MOVETYPE_WALK || this.aiment.classname == "nade") + { + entity aim_ent = ((IS_VEHICLE(this.aiment) && this.aiment.owner) ? this.aiment.owner : this.aiment); + v = v - dv * 0.5; + if((frozen_pulling && STAT(FROZEN, this.aiment)) || !frozen_pulling) + { + this.aiment.velocity = this.aiment.velocity - dv * 0.5; + UNSET_ONGROUND(this.aiment); + if(this.aiment.flags & FL_PROJECTILE) + UpdateCSQCProjectile(this.aiment); + } + if(this.aiment.classname == "nade") + this.aiment.nextthink = time + autocvar_g_balance_grapplehook_nade_time; // set time after letting go? + aim_ent.pusher = this.realowner; + aim_ent.pushltime = time + autocvar_g_maxpushtime; + aim_ent.istypefrag = PHYS_INPUT_BUTTON_CHAT(aim_ent); + } + } + + UNSET_ONGROUND(pull_entity); + } + + if(!frozen_pulling && !(this.aiment.flags & FL_PROJECTILE)) + pull_entity.velocity = WarpZone_RefSys_TransformVelocity(this, pull_entity, v * velocity_multiplier); + + if(frozen_pulling && autocvar_g_balance_grapplehook_pull_frozen == 2 && !STAT(FROZEN, this.aiment)) + { + RemoveHook(this); + return; + } + } + else + { + end = this.origin - dir*50; + dist = vlen(end - myorg); + if(dist < 200) + spd = dist * (pullspeed / 200); + else + spd = pullspeed; + if(spd < 50) + spd = 0; + this.realowner.velocity = dir*spd; + set_movetype(this.realowner, MOVETYPE_FLY); + + UNSET_ONGROUND(this.realowner); + } + } + + makevectors(this.angles.x * '-1 0 0' + this.angles.y * '0 1 0'); + myorg = WarpZone_RefSys_TransformOrigin(this, this.realowner, this.origin); // + v_forward * (-9); + + if(myorg != this.hook_start) + { + this.SendFlags |= 2; + this.hook_start = myorg; + } + if(org != this.hook_end) + { + this.SendFlags |= 4; + this.hook_end = org; + } +} + +void GrapplingHookTouch(entity this, entity toucher) +{ + if(toucher.move_movetype == MOVETYPE_FOLLOW) + return; + PROJECTILE_TOUCH(this, toucher); + + GrapplingHook_Stop(this); + + if(toucher) + //if(toucher.move_movetype != MOVETYPE_NONE) + { + SetMovetypeFollow(this, toucher); + WarpZone_RefSys_BeginAddingIncrementally(this, this.aiment); + } + + //this.realowner.disableclientprediction = true; +} + +void GrapplingHook_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force) +{ + if(GetResource(this, RES_HEALTH) <= 0) + return; + + if (!W_CheckProjectileDamage(inflictor.realowner, this.realowner, deathtype, -1)) // no exceptions + return; // g_balance_projectiledamage says to halt + + TakeResource(this, RES_HEALTH, damage); + + if (GetResource(this, RES_HEALTH) <= 0) + { + if(attacker != this.realowner) + { + this.realowner.pusher = attacker; + this.realowner.pushltime = time + autocvar_g_maxpushtime; + this.realowner.istypefrag = PHYS_INPUT_BUTTON_CHAT(this.realowner); + } + RemoveHook(this); + } +} + +void FireGrapplingHook(entity actor, .entity weaponentity) +{ + if(weaponLocked(actor)) return; + if(actor.vehicle) return; + + int s = W_GunAlign(actor.(weaponentity), STAT(GUNALIGN, actor)) - 1; + vector vs = hook_shotorigin[s]; + vector oldmovedir = actor.(weaponentity).movedir; + actor.(weaponentity).movedir = vs; + W_SetupShot_ProjectileSize(actor, weaponentity, '-3 -3 -3', '3 3 3', true, 0, SND_HOOK_FIRE, CH_WEAPON_B, 0, WEP_HOOK.m_id); + W_MuzzleFlash(WEP_HOOK, actor, weaponentity, w_shotorg, '0 0 0'); + actor.(weaponentity).movedir = oldmovedir; + + entity missile = WarpZone_RefSys_SpawnSameRefSys(actor); + missile.owner = missile.realowner = actor; + actor.(weaponentity).hook = missile; + missile.weaponentity_fld = weaponentity; + missile.reset = GrapplingHookReset; + missile.classname = "grapplinghook"; + missile.flags = FL_PROJECTILE; + IL_PUSH(g_projectiles, missile); + IL_PUSH(g_bot_dodge, missile); + + set_movetype(missile, ((autocvar_g_balance_grapplehook_gravity) ? MOVETYPE_TOSS : MOVETYPE_FLY)); + PROJECTILE_MAKETRIGGER(missile); + + //setmodel (missile, MDL_HOOK); // precision set below + setsize (missile, '-3 -3 -3', '3 3 3'); + setorigin(missile, w_shotorg); + + missile.state = 0; // not latched onto anything + + W_SetupProjVelocity_Explicit(missile, w_shotdir, v_up, autocvar_g_balance_grapplehook_speed_fly, 0, 0, 0, false); + + missile.angles = vectoangles (missile.velocity); + //missile.glow_color = 250; // 244, 250 + //missile.glow_size = 120; + settouch(missile, GrapplingHookTouch); + setthink(missile, GrapplingHookThink); + missile.nextthink = time; + + missile.effects = /*EF_FULLBRIGHT | EF_ADDITIVE |*/ EF_LOWPRECISION; + + SetResourceExplicit(missile, RES_HEALTH, autocvar_g_balance_grapplehook_health); + missile.event_damage = GrapplingHook_Damage; + missile.takedamage = DAMAGE_AIM; + missile.damageforcescale = 0; + missile.damagedbycontents = (autocvar_g_balance_grapplehook_damagedbycontents); + if(missile.damagedbycontents) + IL_PUSH(g_damagedbycontents, missile); + + missile.hook_start = missile.hook_end = missile.origin; + + Net_LinkEntity(missile, false, 0, GrapplingHookSend); +} + +void GrappleHookInit() +{ + if(g_grappling_hook) + { + hook_shotorigin[0] = '8 8 -12'; + hook_shotorigin[1] = '8 8 -12'; + hook_shotorigin[2] = '8 8 -12'; + hook_shotorigin[3] = '8 8 -12'; + } + else + { + Weapon w = WEP_HOOK; + w.wr_init(w); + hook_shotorigin[0] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_HOOK.m_id), false, false, 1); + hook_shotorigin[1] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_HOOK.m_id), false, false, 2); + hook_shotorigin[2] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_HOOK.m_id), false, false, 3); + hook_shotorigin[3] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_HOOK.m_id), false, false, 4); + } +} diff --git a/qcsrc/server/hook.qh b/qcsrc/server/hook.qh new file mode 100644 index 000000000..1ed78e274 --- /dev/null +++ b/qcsrc/server/hook.qh @@ -0,0 +1,23 @@ +#pragma once + +// Wazat's grappling hook +.entity hook; +void GrapplingHookThink(entity this); +void RemoveGrapplingHooks(entity pl); +void RemoveHook(entity this); +// (note: you can change the hook impulse #'s to whatever you please) +.float hook_time; + +.float hook_length; + +const float HOOK_FIRING = BIT(0); +const float HOOK_REMOVING = BIT(1); +const float HOOK_PULLING = BIT(2); +const float HOOK_RELEASING = BIT(3); +const float HOOK_WAITING_FOR_RELEASE = BIT(4); +.float hook_state; +.int state; + +void GrappleHookInit(); +vector hook_shotorigin[4]; + diff --git a/qcsrc/server/impulse.qc b/qcsrc/server/impulse.qc index 521c2223f..779384d70 100644 --- a/qcsrc/server/impulse.qc +++ b/qcsrc/server/impulse.qc @@ -6,7 +6,7 @@ #include "cheats.qh" #include "client.qh" #include "clientkill.qh" -#include "g_damage.qh" +#include "damage.qh" #include "weapons/selection.qh" #include "weapons/tracing.qh" #include "weapons/weaponsystem.qh" diff --git a/qcsrc/server/items/items.qc b/qcsrc/server/items/items.qc index 44cd6ba28..861b21eed 100644 --- a/qcsrc/server/items/items.qc +++ b/qcsrc/server/items/items.qc @@ -6,7 +6,7 @@ #include -#include +#include #include diff --git a/qcsrc/server/main.qc b/qcsrc/server/main.qc new file mode 100644 index 000000000..bbafd022d --- /dev/null +++ b/qcsrc/server/main.qc @@ -0,0 +1,406 @@ +#include "main.qh" + +#include "anticheat.qh" +#include "hook.qh" +#include "damage.qh" +#include "world.qh" +#include "spawnpoints.qh" +#include + +#include "bot/api.qh" + +#include "command/common.qh" + +#include +#include "weapons/csqcprojectile.qh" +#include +#include + +#include "../common/constants.qh" +#include "../common/deathtypes/all.qh" +#include "../common/debug.qh" +#include "../common/mapinfo.qh" +#include "../common/util.qh" + +#include "../common/vehicles/all.qh" +#include +#include + +#include "../lib/csqcmodel/sv_model.qh" + +#include "../lib/warpzone/common.qh" +#include "../lib/warpzone/server.qh" + +void CreatureFrame_hotliquids(entity this) +{ + if (this.contents_damagetime >= time) + { + return; + } + + this.contents_damagetime = time + autocvar_g_balance_contents_damagerate; + + if (this.flags & FL_PROJECTILE) + { + if (this.watertype == CONTENT_LAVA) + Damage (this, NULL, NULL, autocvar_g_balance_contents_projectiledamage * autocvar_g_balance_contents_damagerate * this.waterlevel, DEATH_LAVA.m_id, DMG_NOWEP, this.origin, '0 0 0'); + else if (this.watertype == CONTENT_SLIME) + Damage (this, NULL, NULL, autocvar_g_balance_contents_projectiledamage * autocvar_g_balance_contents_damagerate * this.waterlevel, DEATH_SLIME.m_id, DMG_NOWEP, this.origin, '0 0 0'); + } + else + { + if (STAT(FROZEN, this)) + { + if (this.watertype == CONTENT_LAVA) + Damage(this, NULL, NULL, 10000, DEATH_LAVA.m_id, DMG_NOWEP, this.origin, '0 0 0'); + else if (this.watertype == CONTENT_SLIME) + Damage(this, NULL, NULL, 10000, DEATH_SLIME.m_id, DMG_NOWEP, this.origin, '0 0 0'); + } + else if (this.watertype == CONTENT_LAVA) + { + if (this.watersound_finished < time) + { + this.watersound_finished = time + 0.5; + sound (this, CH_PLAYER_SINGLE, SND_LAVA, VOL_BASE, ATTEN_NORM); + } + Damage (this, NULL, NULL, autocvar_g_balance_contents_playerdamage_lava * autocvar_g_balance_contents_damagerate * this.waterlevel, DEATH_LAVA.m_id, DMG_NOWEP, this.origin, '0 0 0'); + if(autocvar_g_balance_contents_playerdamage_lava_burn) + Fire_AddDamage(this, NULL, autocvar_g_balance_contents_playerdamage_lava_burn * this.waterlevel, autocvar_g_balance_contents_playerdamage_lava_burn_time * this.waterlevel, DEATH_LAVA.m_id); + } + else if (this.watertype == CONTENT_SLIME) + { + if (this.watersound_finished < time) + { + this.watersound_finished = time + 0.5; + sound (this, CH_PLAYER_SINGLE, SND_SLIME, VOL_BASE, ATTEN_NORM); + } + Damage (this, NULL, NULL, autocvar_g_balance_contents_playerdamage_slime * autocvar_g_balance_contents_damagerate * this.waterlevel, DEATH_SLIME.m_id, DMG_NOWEP, this.origin, '0 0 0'); + } + } +} + +void CreatureFrame_Liquids(entity this) +{ + if (this.watertype <= CONTENT_WATER && this.waterlevel > 0) // workaround a retarded bug made by id software :P (yes, it's that old of a bug) + { + if (!(this.flags & FL_INWATER)) + { + this.flags |= FL_INWATER; + this.contents_damagetime = 0; + } + + CreatureFrame_hotliquids(this); + } + else + { + if (this.flags & FL_INWATER) + { + // play leave water sound + this.flags &= ~FL_INWATER; + this.contents_damagetime = 0; + } + } +} + +void CreatureFrame_FallDamage(entity this) +{ + if(IS_VEHICLE(this) || (this.flags & FL_PROJECTILE)) + return; // vehicles and projectiles don't receive fall damage + if(!(this.velocity || this.oldvelocity)) + return; // if the entity hasn't moved and isn't moving, then don't do anything + + // check for falling damage + bool have_hook = false; + for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) + { + .entity weaponentity = weaponentities[slot]; + if(this.(weaponentity).hook && this.(weaponentity).hook.state) + { + have_hook = true; + break; + } + } + if(!have_hook) + { + float dm; // dm is the velocity DECREASE. Velocity INCREASE should never cause a sound or any damage. + if(autocvar_g_balance_falldamage_onlyvertical) + dm = fabs(this.oldvelocity.z) - vlen(this.velocity); + else + dm = vlen(this.oldvelocity) - vlen(this.velocity); + if (IS_DEAD(this)) + dm = (dm - autocvar_g_balance_falldamage_deadminspeed) * autocvar_g_balance_falldamage_factor; + else + dm = min((dm - autocvar_g_balance_falldamage_minspeed) * autocvar_g_balance_falldamage_factor, autocvar_g_balance_falldamage_maxdamage); + if (dm > 0) + { + tracebox(this.origin, this.mins, this.maxs, this.origin - '0 0 1', MOVE_NOMONSTERS, this); + if (!(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NODAMAGE)) + Damage (this, NULL, NULL, dm, DEATH_FALL.m_id, DMG_NOWEP, this.origin, '0 0 0'); + } + } + + if(autocvar_g_maxspeed > 0 && vdist(this.velocity, >, autocvar_g_maxspeed)) + Damage (this, NULL, NULL, 100000, DEATH_SHOOTING_STAR.m_id, DMG_NOWEP, this.origin, '0 0 0'); +} + +void CreatureFrame_All() +{ + if(game_stopped || time < game_starttime) + return; + + IL_EACH(g_damagedbycontents, it.damagedbycontents, + { + if (it.move_movetype == MOVETYPE_NOCLIP) continue; + CreatureFrame_Liquids(it); + CreatureFrame_FallDamage(it); + it.oldvelocity = it.velocity; + }); +} + +void Pause_TryPause(bool ispaused) +{ + int n = 0; + FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), { + if (PHYS_INPUT_BUTTON_CHAT(it) != ispaused) return; + ++n; + }); + if (!n) return; + setpause(ispaused); +} + +void SV_PausedTic(float elapsedtime) +{ + if (!server_is_dedicated) Pause_TryPause(false); +} + +/* +============= +StartFrame + +Called before each frame by the server +============= +*/ + +bool game_delay_last; + +bool autocvar_sv_autopause = false; +void systems_update(); +void sys_phys_update(entity this, float dt); +void StartFrame() +{ + // TODO: if move is more than 50ms, split it into two moves (this matches QWSV behavior and the client prediction) + IL_EACH(g_players, IS_FAKE_CLIENT(it), sys_phys_update(it, frametime)); + IL_EACH(g_players, IS_FAKE_CLIENT(it), PlayerPreThink(it)); + + execute_next_frame(); + if (autocvar_sv_autopause && !server_is_dedicated) Pause_TryPause(true); + + delete_fn = remove_unsafely; // not during spawning! + serverprevtime = servertime; + servertime = time; + serverframetime = frametime; + +#ifdef PROFILING + if(time > client_cefc_accumulatortime + 1) + { + float t = client_cefc_accumulator / (time - client_cefc_accumulatortime); + int c_seeing = 0; + int c_seen = 0; + FOREACH_CLIENT(true, { + if(IS_REAL_CLIENT(it)) + ++c_seeing; + if(IS_PLAYER(it)) + ++c_seen; + }); + LOG_INFO( + "CEFC time: ", ftos(t * 1000), "ms; ", + "CEFC calls per second: ", ftos(c_seeing * (c_seen - 1) / t), "; ", + "CEFC 100% load at: ", ftos(solve_quadratic(t, -t, -1) * '0 1 0') + ); + client_cefc_accumulatortime = time; + client_cefc_accumulator = 0; + } +#endif + + IL_EACH(g_projectiles, it.csqcprojectile_clientanimate, CSQCProjectile_Check(it)); + + if (RedirectionThink()) return; + + UncustomizeEntitiesRun(); + InitializeEntitiesRun(); + + WarpZone_StartFrame(); + + sys_frametime = autocvar_sys_ticrate * autocvar_slowmo; + if (sys_frametime <= 0) sys_frametime = 1.0 / 60.0; // somewhat safe fallback + + if (timeout_status == TIMEOUT_LEADTIME) // just before the timeout (when timeout_status will be TIMEOUT_ACTIVE) + orig_slowmo = autocvar_slowmo; // slowmo will be restored after the timeout + + // detect when the pre-game countdown (if any) has ended and the game has started + bool game_delay = (time < game_starttime); + if (autocvar_sv_eventlog && game_delay_last && !game_delay) + GameLogEcho(":startdelay_ended"); + game_delay_last = game_delay; + + CreatureFrame_All(); + CheckRules_World(); + + if (warmup_stage && !game_stopped && warmup_limit > 0 && time >= warmup_limit) { + ReadyRestart(); + return; + } + + bot_serverframe(); + anticheat_startframe(); + MUTATOR_CALLHOOK(SV_StartFrame); + + GlobalStats_updateglobal(); + FOREACH_CLIENT(true, GlobalStats_update(it)); + IL_EACH(g_players, IS_FAKE_CLIENT(it), PlayerPostThink(it)); +} + +.vector originjitter; +.vector anglesjitter; +.float anglejitter; +.string gametypefilter; +.string cvarfilter; + +/** + * Evaluate an expression of the form: [+ | -]? [var[op]val | [op]var | val | var] ... + * +: all must match. this is the default + * -: one must NOT match + * + * var>x + * var=x + * var<=x + * var==x + * var!=x + * var===x + * var!==x + */ +bool expr_evaluate(string s) +{ + bool ret = false; + if (str2chr(s, 0) == '+') { + s = substring(s, 1, -1); + } else if (str2chr(s, 0) == '-') { + ret = true; + s = substring(s, 1, -1); + } + bool expr_fail = false; + for (int i = 0, n = tokenize_console(s); i < n; ++i) { + int o; + string k, v; + s = argv(i); + #define X(expr) \ + if (expr) \ + continue; \ + expr_fail = true; \ + break; + + #define BINOP(op, len, expr) \ + if ((o = strstrofs(s, op, 0)) >= 0) { \ + k = substring(s, 0, o); \ + v = substring(s, o + len, -1); \ + X(expr); \ + } + BINOP(">=", 2, cvar(k) >= stof(v)); + BINOP("<=", 2, cvar(k) <= stof(v)); + BINOP(">", 1, cvar(k) > stof(v)); + BINOP("<", 1, cvar(k) < stof(v)); + BINOP("==", 2, cvar(k) == stof(v)); + BINOP("!=", 2, cvar(k) != stof(v)); + BINOP("===", 3, cvar_string(k) == v); + BINOP("!==", 3, cvar_string(k) != v); + { + k = s; + bool b = true; + if (str2chr(k, 0) == '!') { + k = substring(s, 1, -1); + b = false; + } + float f = stof(k); + bool isnum = ftos(f) == k; + X(boolean(isnum ? f : cvar(k)) == b); + } + #undef BINOP + #undef X + } + if (!expr_fail) { + ret = !ret; + } + // now ret is true if we want to keep the item, and false if we want to get rid of it + return ret; +} + +void SV_OnEntityPreSpawnFunction(entity this) +{ + if (this) + if (this.gametypefilter != "") + if (!isGametypeInFilter(MapInfo_LoadedGametype, teamplay, have_team_spawns, this.gametypefilter)) + { + delete(this); + return; + } + if (this.cvarfilter != "" && !expr_evaluate(this.cvarfilter)) { + delete(this); + return; + } + + if (DoesQ3ARemoveThisEntity(this)) { + delete(this); + return; + } + + set_movetype(this, this.movetype); + + if (this.monster_attack) { + IL_PUSH(g_monster_targets, this); + } + + // support special -1 and -2 angle from radiant + if (this.angles == '0 -1 0') { + this.angles = '-90 0 0'; + } else if (this.angles == '0 -2 0') { + this.angles = '+90 0 0'; + } + + #define X(out, in) MACRO_BEGIN \ + if (in != 0) { out = out + (random() * 2 - 1) * in; } \ + MACRO_END + X(this.origin.x, this.originjitter.x); X(this.origin.y, this.originjitter.y); X(this.origin.z, this.originjitter.z); + X(this.angles.x, this.anglesjitter.x); X(this.angles.y, this.anglesjitter.y); X(this.angles.z, this.anglesjitter.z); + X(this.angles.y, this.anglejitter); + #undef X + + if (MUTATOR_CALLHOOK(OnEntityPreSpawn, this)) { + delete(this); + return; + } +} + +void WarpZone_PostInitialize_Callback() +{ + // create waypoint links for warpzones + entity tracetest_ent = spawn(); + setsize(tracetest_ent, PL_MIN_CONST, PL_MAX_CONST); + tracetest_ent.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP; + //for(entity e = warpzone_first; e; e = e.warpzone_next) + for(entity e = NULL; (e = find(e, classname, "trigger_warpzone")); ) + waypoint_spawnforteleporter_wz(e, tracetest_ent); + delete(tracetest_ent); +} + +/* +================== +main + +unused but required by the engine +================== +*/ +void main () +{ + +} diff --git a/qcsrc/server/main.qh b/qcsrc/server/main.qh new file mode 100644 index 000000000..dc3d80dbe --- /dev/null +++ b/qcsrc/server/main.qh @@ -0,0 +1,28 @@ +#pragma once + +bool expr_evaluate(string s); + +#ifdef PROFILING +float client_cefc_accumulator; +float client_cefc_accumulatortime; +#endif + +float servertime, serverprevtime, serverframetime; + +.vector oldvelocity; // for fall damage + +.float watersound_finished; + +.bool iscreature; +.float species; + +.float contents_damagetime; + +/* +================== +main + +unused but required by the engine +================== +*/ +void main (); diff --git a/qcsrc/server/mapvoting.qc b/qcsrc/server/mapvoting.qc index 2286b60a4..ac64f630f 100644 --- a/qcsrc/server/mapvoting.qc +++ b/qcsrc/server/mapvoting.qc @@ -5,7 +5,7 @@ #include #include #include -#include "g_world.qh" +#include "world.qh" #include "command/cmd.qh" #include "command/getreplies.qh" #include "../common/constants.qh" diff --git a/qcsrc/server/miscfunctions.qc b/qcsrc/server/miscfunctions.qc index 76dcf8491..1a751984d 100644 --- a/qcsrc/server/miscfunctions.qc +++ b/qcsrc/server/miscfunctions.qc @@ -4,15 +4,15 @@ #include "command/common.qh" #include "client.qh" #include "constants.qh" -#include "g_damage.qh" -#include "g_hook.qh" -#include "g_world.qh" +#include "damage.qh" +#include "hook.qh" +#include "world.qh" #include #include "ipban.qh" #include #include #include -#include +#include #include "mapvoting.qh" #include "resources.qh" #include diff --git a/qcsrc/server/miscfunctions.qh b/qcsrc/server/miscfunctions.qh index 6d317a71c..8a2406316 100644 --- a/qcsrc/server/miscfunctions.qh +++ b/qcsrc/server/miscfunctions.qh @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include diff --git a/qcsrc/server/player.qc b/qcsrc/server/player.qc index ba19f16a7..ef6045403 100644 --- a/qcsrc/server/player.qc +++ b/qcsrc/server/player.qc @@ -5,13 +5,13 @@ #include "cheats.qh" #include "client.qh" #include "clientkill.qh" -#include "g_damage.qh" -#include "g_world.qh" +#include "damage.qh" +#include "world.qh" #include "handicap.qh" #include "miscfunctions.qh" #include "portals.qh" #include "teamplay.qh" -#include +#include #include "weapons/common.qh" #include "weapons/throwing.qh" #include "command/common.qh" diff --git a/qcsrc/server/portals.qc b/qcsrc/server/portals.qc index 7edbc6f99..7abe6ae97 100644 --- a/qcsrc/server/portals.qc +++ b/qcsrc/server/portals.qc @@ -1,7 +1,7 @@ #include "portals.qh" #include -#include "g_hook.qh" +#include "hook.qh" #include "mutators/_mod.qh" #include #include @@ -21,7 +21,7 @@ #include "../common/vehicles/sv_vehicles.qh" #include #include -#include +#include #define PORTALS_ARE_NOT_SOLID diff --git a/qcsrc/server/race.qc b/qcsrc/server/race.qc index 7b99d916a..c2de3c9cc 100644 --- a/qcsrc/server/race.qc +++ b/qcsrc/server/race.qc @@ -2,8 +2,8 @@ #include #include -#include -#include +#include +#include #include #include #include "client.qh" diff --git a/qcsrc/server/round_handler.qc b/qcsrc/server/round_handler.qc index aab069cba..66da6c37c 100644 --- a/qcsrc/server/round_handler.qc +++ b/qcsrc/server/round_handler.qc @@ -1,6 +1,6 @@ #include "round_handler.qh" -#include +#include #include #include "campaign.qh" #include "command/vote.qh" diff --git a/qcsrc/server/scores.qc b/qcsrc/server/scores.qc index 18bb5f76c..e4e1d236a 100644 --- a/qcsrc/server/scores.qc +++ b/qcsrc/server/scores.qc @@ -3,7 +3,7 @@ #include "command/common.qh" #include #include "client.qh" -#include +#include #include #include #include diff --git a/qcsrc/server/spawnpoints.qc b/qcsrc/server/spawnpoints.qc index 9884ec75e..b629ef168 100644 --- a/qcsrc/server/spawnpoints.qc +++ b/qcsrc/server/spawnpoints.qc @@ -1,7 +1,7 @@ #include "spawnpoints.qh" #include -#include "g_world.qh" +#include "world.qh" #include "miscfunctions.qh" #include "race.qh" #include diff --git a/qcsrc/server/sv_main.qc b/qcsrc/server/sv_main.qc deleted file mode 100644 index 4969c5105..000000000 --- a/qcsrc/server/sv_main.qc +++ /dev/null @@ -1,406 +0,0 @@ -#include "sv_main.qh" - -#include "anticheat.qh" -#include "g_hook.qh" -#include "g_damage.qh" -#include "g_world.qh" -#include "spawnpoints.qh" -#include - -#include "bot/api.qh" - -#include "command/common.qh" - -#include -#include "weapons/csqcprojectile.qh" -#include -#include - -#include "../common/constants.qh" -#include "../common/deathtypes/all.qh" -#include "../common/debug.qh" -#include "../common/mapinfo.qh" -#include "../common/util.qh" - -#include "../common/vehicles/all.qh" -#include -#include - -#include "../lib/csqcmodel/sv_model.qh" - -#include "../lib/warpzone/common.qh" -#include "../lib/warpzone/server.qh" - -void CreatureFrame_hotliquids(entity this) -{ - if (this.contents_damagetime >= time) - { - return; - } - - this.contents_damagetime = time + autocvar_g_balance_contents_damagerate; - - if (this.flags & FL_PROJECTILE) - { - if (this.watertype == CONTENT_LAVA) - Damage (this, NULL, NULL, autocvar_g_balance_contents_projectiledamage * autocvar_g_balance_contents_damagerate * this.waterlevel, DEATH_LAVA.m_id, DMG_NOWEP, this.origin, '0 0 0'); - else if (this.watertype == CONTENT_SLIME) - Damage (this, NULL, NULL, autocvar_g_balance_contents_projectiledamage * autocvar_g_balance_contents_damagerate * this.waterlevel, DEATH_SLIME.m_id, DMG_NOWEP, this.origin, '0 0 0'); - } - else - { - if (STAT(FROZEN, this)) - { - if (this.watertype == CONTENT_LAVA) - Damage(this, NULL, NULL, 10000, DEATH_LAVA.m_id, DMG_NOWEP, this.origin, '0 0 0'); - else if (this.watertype == CONTENT_SLIME) - Damage(this, NULL, NULL, 10000, DEATH_SLIME.m_id, DMG_NOWEP, this.origin, '0 0 0'); - } - else if (this.watertype == CONTENT_LAVA) - { - if (this.watersound_finished < time) - { - this.watersound_finished = time + 0.5; - sound (this, CH_PLAYER_SINGLE, SND_LAVA, VOL_BASE, ATTEN_NORM); - } - Damage (this, NULL, NULL, autocvar_g_balance_contents_playerdamage_lava * autocvar_g_balance_contents_damagerate * this.waterlevel, DEATH_LAVA.m_id, DMG_NOWEP, this.origin, '0 0 0'); - if(autocvar_g_balance_contents_playerdamage_lava_burn) - Fire_AddDamage(this, NULL, autocvar_g_balance_contents_playerdamage_lava_burn * this.waterlevel, autocvar_g_balance_contents_playerdamage_lava_burn_time * this.waterlevel, DEATH_LAVA.m_id); - } - else if (this.watertype == CONTENT_SLIME) - { - if (this.watersound_finished < time) - { - this.watersound_finished = time + 0.5; - sound (this, CH_PLAYER_SINGLE, SND_SLIME, VOL_BASE, ATTEN_NORM); - } - Damage (this, NULL, NULL, autocvar_g_balance_contents_playerdamage_slime * autocvar_g_balance_contents_damagerate * this.waterlevel, DEATH_SLIME.m_id, DMG_NOWEP, this.origin, '0 0 0'); - } - } -} - -void CreatureFrame_Liquids(entity this) -{ - if (this.watertype <= CONTENT_WATER && this.waterlevel > 0) // workaround a retarded bug made by id software :P (yes, it's that old of a bug) - { - if (!(this.flags & FL_INWATER)) - { - this.flags |= FL_INWATER; - this.contents_damagetime = 0; - } - - CreatureFrame_hotliquids(this); - } - else - { - if (this.flags & FL_INWATER) - { - // play leave water sound - this.flags &= ~FL_INWATER; - this.contents_damagetime = 0; - } - } -} - -void CreatureFrame_FallDamage(entity this) -{ - if(IS_VEHICLE(this) || (this.flags & FL_PROJECTILE)) - return; // vehicles and projectiles don't receive fall damage - if(!(this.velocity || this.oldvelocity)) - return; // if the entity hasn't moved and isn't moving, then don't do anything - - // check for falling damage - bool have_hook = false; - for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) - { - .entity weaponentity = weaponentities[slot]; - if(this.(weaponentity).hook && this.(weaponentity).hook.state) - { - have_hook = true; - break; - } - } - if(!have_hook) - { - float dm; // dm is the velocity DECREASE. Velocity INCREASE should never cause a sound or any damage. - if(autocvar_g_balance_falldamage_onlyvertical) - dm = fabs(this.oldvelocity.z) - vlen(this.velocity); - else - dm = vlen(this.oldvelocity) - vlen(this.velocity); - if (IS_DEAD(this)) - dm = (dm - autocvar_g_balance_falldamage_deadminspeed) * autocvar_g_balance_falldamage_factor; - else - dm = min((dm - autocvar_g_balance_falldamage_minspeed) * autocvar_g_balance_falldamage_factor, autocvar_g_balance_falldamage_maxdamage); - if (dm > 0) - { - tracebox(this.origin, this.mins, this.maxs, this.origin - '0 0 1', MOVE_NOMONSTERS, this); - if (!(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NODAMAGE)) - Damage (this, NULL, NULL, dm, DEATH_FALL.m_id, DMG_NOWEP, this.origin, '0 0 0'); - } - } - - if(autocvar_g_maxspeed > 0 && vdist(this.velocity, >, autocvar_g_maxspeed)) - Damage (this, NULL, NULL, 100000, DEATH_SHOOTING_STAR.m_id, DMG_NOWEP, this.origin, '0 0 0'); -} - -void CreatureFrame_All() -{ - if(game_stopped || time < game_starttime) - return; - - IL_EACH(g_damagedbycontents, it.damagedbycontents, - { - if (it.move_movetype == MOVETYPE_NOCLIP) continue; - CreatureFrame_Liquids(it); - CreatureFrame_FallDamage(it); - it.oldvelocity = it.velocity; - }); -} - -void Pause_TryPause(bool ispaused) -{ - int n = 0; - FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), { - if (PHYS_INPUT_BUTTON_CHAT(it) != ispaused) return; - ++n; - }); - if (!n) return; - setpause(ispaused); -} - -void SV_PausedTic(float elapsedtime) -{ - if (!server_is_dedicated) Pause_TryPause(false); -} - -/* -============= -StartFrame - -Called before each frame by the server -============= -*/ - -bool game_delay_last; - -bool autocvar_sv_autopause = false; -void systems_update(); -void sys_phys_update(entity this, float dt); -void StartFrame() -{ - // TODO: if move is more than 50ms, split it into two moves (this matches QWSV behavior and the client prediction) - IL_EACH(g_players, IS_FAKE_CLIENT(it), sys_phys_update(it, frametime)); - IL_EACH(g_players, IS_FAKE_CLIENT(it), PlayerPreThink(it)); - - execute_next_frame(); - if (autocvar_sv_autopause && !server_is_dedicated) Pause_TryPause(true); - - delete_fn = remove_unsafely; // not during spawning! - serverprevtime = servertime; - servertime = time; - serverframetime = frametime; - -#ifdef PROFILING - if(time > client_cefc_accumulatortime + 1) - { - float t = client_cefc_accumulator / (time - client_cefc_accumulatortime); - int c_seeing = 0; - int c_seen = 0; - FOREACH_CLIENT(true, { - if(IS_REAL_CLIENT(it)) - ++c_seeing; - if(IS_PLAYER(it)) - ++c_seen; - }); - LOG_INFO( - "CEFC time: ", ftos(t * 1000), "ms; ", - "CEFC calls per second: ", ftos(c_seeing * (c_seen - 1) / t), "; ", - "CEFC 100% load at: ", ftos(solve_quadratic(t, -t, -1) * '0 1 0') - ); - client_cefc_accumulatortime = time; - client_cefc_accumulator = 0; - } -#endif - - IL_EACH(g_projectiles, it.csqcprojectile_clientanimate, CSQCProjectile_Check(it)); - - if (RedirectionThink()) return; - - UncustomizeEntitiesRun(); - InitializeEntitiesRun(); - - WarpZone_StartFrame(); - - sys_frametime = autocvar_sys_ticrate * autocvar_slowmo; - if (sys_frametime <= 0) sys_frametime = 1.0 / 60.0; // somewhat safe fallback - - if (timeout_status == TIMEOUT_LEADTIME) // just before the timeout (when timeout_status will be TIMEOUT_ACTIVE) - orig_slowmo = autocvar_slowmo; // slowmo will be restored after the timeout - - // detect when the pre-game countdown (if any) has ended and the game has started - bool game_delay = (time < game_starttime); - if (autocvar_sv_eventlog && game_delay_last && !game_delay) - GameLogEcho(":startdelay_ended"); - game_delay_last = game_delay; - - CreatureFrame_All(); - CheckRules_World(); - - if (warmup_stage && !game_stopped && warmup_limit > 0 && time >= warmup_limit) { - ReadyRestart(); - return; - } - - bot_serverframe(); - anticheat_startframe(); - MUTATOR_CALLHOOK(SV_StartFrame); - - GlobalStats_updateglobal(); - FOREACH_CLIENT(true, GlobalStats_update(it)); - IL_EACH(g_players, IS_FAKE_CLIENT(it), PlayerPostThink(it)); -} - -.vector originjitter; -.vector anglesjitter; -.float anglejitter; -.string gametypefilter; -.string cvarfilter; - -/** - * Evaluate an expression of the form: [+ | -]? [var[op]val | [op]var | val | var] ... - * +: all must match. this is the default - * -: one must NOT match - * - * var>x - * var=x - * var<=x - * var==x - * var!=x - * var===x - * var!==x - */ -bool expr_evaluate(string s) -{ - bool ret = false; - if (str2chr(s, 0) == '+') { - s = substring(s, 1, -1); - } else if (str2chr(s, 0) == '-') { - ret = true; - s = substring(s, 1, -1); - } - bool expr_fail = false; - for (int i = 0, n = tokenize_console(s); i < n; ++i) { - int o; - string k, v; - s = argv(i); - #define X(expr) \ - if (expr) \ - continue; \ - expr_fail = true; \ - break; - - #define BINOP(op, len, expr) \ - if ((o = strstrofs(s, op, 0)) >= 0) { \ - k = substring(s, 0, o); \ - v = substring(s, o + len, -1); \ - X(expr); \ - } - BINOP(">=", 2, cvar(k) >= stof(v)); - BINOP("<=", 2, cvar(k) <= stof(v)); - BINOP(">", 1, cvar(k) > stof(v)); - BINOP("<", 1, cvar(k) < stof(v)); - BINOP("==", 2, cvar(k) == stof(v)); - BINOP("!=", 2, cvar(k) != stof(v)); - BINOP("===", 3, cvar_string(k) == v); - BINOP("!==", 3, cvar_string(k) != v); - { - k = s; - bool b = true; - if (str2chr(k, 0) == '!') { - k = substring(s, 1, -1); - b = false; - } - float f = stof(k); - bool isnum = ftos(f) == k; - X(boolean(isnum ? f : cvar(k)) == b); - } - #undef BINOP - #undef X - } - if (!expr_fail) { - ret = !ret; - } - // now ret is true if we want to keep the item, and false if we want to get rid of it - return ret; -} - -void SV_OnEntityPreSpawnFunction(entity this) -{ - if (this) - if (this.gametypefilter != "") - if (!isGametypeInFilter(MapInfo_LoadedGametype, teamplay, have_team_spawns, this.gametypefilter)) - { - delete(this); - return; - } - if (this.cvarfilter != "" && !expr_evaluate(this.cvarfilter)) { - delete(this); - return; - } - - if (DoesQ3ARemoveThisEntity(this)) { - delete(this); - return; - } - - set_movetype(this, this.movetype); - - if (this.monster_attack) { - IL_PUSH(g_monster_targets, this); - } - - // support special -1 and -2 angle from radiant - if (this.angles == '0 -1 0') { - this.angles = '-90 0 0'; - } else if (this.angles == '0 -2 0') { - this.angles = '+90 0 0'; - } - - #define X(out, in) MACRO_BEGIN \ - if (in != 0) { out = out + (random() * 2 - 1) * in; } \ - MACRO_END - X(this.origin.x, this.originjitter.x); X(this.origin.y, this.originjitter.y); X(this.origin.z, this.originjitter.z); - X(this.angles.x, this.anglesjitter.x); X(this.angles.y, this.anglesjitter.y); X(this.angles.z, this.anglesjitter.z); - X(this.angles.y, this.anglejitter); - #undef X - - if (MUTATOR_CALLHOOK(OnEntityPreSpawn, this)) { - delete(this); - return; - } -} - -void WarpZone_PostInitialize_Callback() -{ - // create waypoint links for warpzones - entity tracetest_ent = spawn(); - setsize(tracetest_ent, PL_MIN_CONST, PL_MAX_CONST); - tracetest_ent.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP; - //for(entity e = warpzone_first; e; e = e.warpzone_next) - for(entity e = NULL; (e = find(e, classname, "trigger_warpzone")); ) - waypoint_spawnforteleporter_wz(e, tracetest_ent); - delete(tracetest_ent); -} - -/* -================== -main - -unused but required by the engine -================== -*/ -void main () -{ - -} diff --git a/qcsrc/server/sv_main.qh b/qcsrc/server/sv_main.qh deleted file mode 100644 index dc3d80dbe..000000000 --- a/qcsrc/server/sv_main.qh +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -bool expr_evaluate(string s); - -#ifdef PROFILING -float client_cefc_accumulator; -float client_cefc_accumulatortime; -#endif - -float servertime, serverprevtime, serverframetime; - -.vector oldvelocity; // for fall damage - -.float watersound_finished; - -.bool iscreature; -.float species; - -.float contents_damagetime; - -/* -================== -main - -unused but required by the engine -================== -*/ -void main (); diff --git a/qcsrc/server/teamplay.qc b/qcsrc/server/teamplay.qc index ce41941c5..1c436da96 100644 --- a/qcsrc/server/teamplay.qc +++ b/qcsrc/server/teamplay.qc @@ -2,7 +2,7 @@ #include "client.qh" #include -#include +#include #include "race.qh" #include "scores.qh" #include "scores_rules.qh" diff --git a/qcsrc/server/weapons/accuracy.qc b/qcsrc/server/weapons/accuracy.qc index 0b895ea37..2ec4fc710 100644 --- a/qcsrc/server/weapons/accuracy.qc +++ b/qcsrc/server/weapons/accuracy.qc @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include #include diff --git a/qcsrc/server/weapons/common.qc b/qcsrc/server/weapons/common.qc index 4c9f78422..7cd56faa7 100644 --- a/qcsrc/server/weapons/common.qc +++ b/qcsrc/server/weapons/common.qc @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include #include diff --git a/qcsrc/server/weapons/hitplot.qc b/qcsrc/server/weapons/hitplot.qc index eca09ebaa..0407ad799 100644 --- a/qcsrc/server/weapons/hitplot.qc +++ b/qcsrc/server/weapons/hitplot.qc @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include #include "../antilag.qh" #include diff --git a/qcsrc/server/weapons/throwing.qc b/qcsrc/server/weapons/throwing.qc index 395bfb9b1..2408adbf1 100644 --- a/qcsrc/server/weapons/throwing.qc +++ b/qcsrc/server/weapons/throwing.qc @@ -5,7 +5,7 @@ #include #include #include -#include "../g_damage.qh" +#include "../damage.qh" #include #include #include diff --git a/qcsrc/server/weapons/tracing.qc b/qcsrc/server/weapons/tracing.qc index 2d9972551..f4de0e1bf 100644 --- a/qcsrc/server/weapons/tracing.qc +++ b/qcsrc/server/weapons/tracing.qc @@ -7,8 +7,8 @@ #include "hitplot.qh" #include "weaponsystem.qh" -#include "../g_damage.qh" -#include +#include "../damage.qh" +#include #include "../antilag.qh" #include diff --git a/qcsrc/server/weapons/weaponstats.qc b/qcsrc/server/weapons/weaponstats.qc index eadd6f4bc..552ab6731 100644 --- a/qcsrc/server/weapons/weaponstats.qc +++ b/qcsrc/server/weapons/weaponstats.qc @@ -3,7 +3,7 @@ #include #include #include -#include "../g_world.qh" +#include "../world.qh" #include diff --git a/qcsrc/server/weapons/weaponsystem.qc b/qcsrc/server/weapons/weaponsystem.qc index 81b25e845..763881388 100644 --- a/qcsrc/server/weapons/weaponsystem.qc +++ b/qcsrc/server/weapons/weaponsystem.qc @@ -4,8 +4,8 @@ #include "../command/common.qh" #include -#include -#include +#include +#include #include #include #include "../round_handler.qh" diff --git a/qcsrc/server/world.qc b/qcsrc/server/world.qc new file mode 100644 index 000000000..c9fec23b8 --- /dev/null +++ b/qcsrc/server/world.qc @@ -0,0 +1,2266 @@ +#include "world.qh" + +#include "anticheat.qh" +#include "antilag.qh" +#include "bot/api.qh" +#include "campaign.qh" +#include "cheats.qh" +#include "client.qh" +#include "command/common.qh" +#include "command/getreplies.qh" +#include "command/sv_cmd.qh" +#include "command/vote.qh" +#include "hook.qh" +#include +#include +#include "ipban.qh" +#include "mapvoting.qh" +#include +#include "race.qh" +#include "scores.qh" +#include "scores_rules.qh" +#include "spawnpoints.qh" +#include "teamplay.qh" +#include "weapons/weaponstats.qh" +#include +#include "../common/constants.qh" +#include +#include "../common/deathtypes/all.qh" +#include +#include "../common/gamemodes/sv_rules.qh" +#include "../common/mapinfo.qh" +#include "../common/monsters/_mod.qh" +#include "../common/monsters/sv_monsters.qh" +#include "../common/vehicles/all.qh" +#include "../common/notifications/all.qh" +#include "../common/physics/player.qh" +#include "../common/playerstats.qh" +#include "../common/stats.qh" +#include "../common/teams.qh" +#include +#include "../common/mapobjects/trigger/secret.qh" +#include "../common/mapobjects/target/music.qh" +#include "../common/util.qh" +#include "../common/items/_mod.qh" +#include +#include "../common/state.qh" + +const float LATENCY_THINKRATE = 10; +.float latency_sum; +.float latency_cnt; +.float latency_time; +entity pingplreport; +void PingPLReport_Think(entity this) +{ + float delta; + entity e; + + delta = 3 / maxclients; + if(delta < sys_frametime) + delta = 0; + this.nextthink = time + delta; + + e = edict_num(this.cnt + 1); + if(IS_CLIENT(e) && IS_REAL_CLIENT(e)) + { + WriteHeader(MSG_BROADCAST, TE_CSQC_PINGPLREPORT); + WriteByte(MSG_BROADCAST, this.cnt); + WriteShort(MSG_BROADCAST, bound(1, CS(e).ping, 65535)); + WriteByte(MSG_BROADCAST, min(ceil(CS(e).ping_packetloss * 255), 255)); + WriteByte(MSG_BROADCAST, min(ceil(CS(e).ping_movementloss * 255), 255)); + + // record latency times for clients throughout the match so we can report it to playerstats + if(time > (CS(e).latency_time + LATENCY_THINKRATE)) + { + CS(e).latency_sum += CS(e).ping; + CS(e).latency_cnt += 1; + CS(e).latency_time = time; + //print("sum: ", ftos(CS(e).latency_sum), ", cnt: ", ftos(CS(e).latency_cnt), ", avg: ", ftos(CS(e).latency_sum / CS(e).latency_cnt), ".\n"); + } + } + else + { + WriteHeader(MSG_BROADCAST, TE_CSQC_PINGPLREPORT); + WriteByte(MSG_BROADCAST, this.cnt); + WriteShort(MSG_BROADCAST, 0); + WriteByte(MSG_BROADCAST, 0); + WriteByte(MSG_BROADCAST, 0); + } + this.cnt = (this.cnt + 1) % maxclients; +} +void PingPLReport_Spawn() +{ + pingplreport = new_pure(pingplreport); + setthink(pingplreport, PingPLReport_Think); + pingplreport.nextthink = time; +} + +const float SPAWNFLAG_NO_WAYPOINTS_FOR_ITEMS = 1; +string redirection_target; +float world_initialized; + +void SetDefaultAlpha() +{ + if (!MUTATOR_CALLHOOK(SetDefaultAlpha)) + { + default_player_alpha = autocvar_g_player_alpha; + if(default_player_alpha == 0) + default_player_alpha = 1; + default_weapon_alpha = default_player_alpha; + } +} + +void GotoFirstMap(entity this) +{ + float n; + if(autocvar__sv_init) + { + // cvar_set("_sv_init", "0"); + // we do NOT set this to 0 any more, so someone "accidentally" changing + // to this "init" map on a dedicated server will cause no permanent + // harm + if(autocvar_g_maplist_shuffle) + ShuffleMaplist(); + n = tokenizebyseparator(autocvar_g_maplist, " "); + cvar_set("g_maplist_index", ftos(n - 1)); // jump to map 0 in GotoNextMap + + MapInfo_Enumerate(); + MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags(), 0); + + if(!DoNextMapOverride(1)) + GotoNextMap(1); + + return; + } + + if(time < 5) + { + this.nextthink = time; + } + else + { + this.nextthink = time + 1; + LOG_INFO("Waiting for _sv_init being set to 1 by initialization scripts..."); + } +} + +void cvar_changes_init() +{ + float h; + string k, v, d; + float n, i, adding, pureadding; + + strfree(cvar_changes); + strfree(cvar_purechanges); + cvar_purechanges_count = 0; + + h = buf_create(); + buf_cvarlist(h, "", "_"); // exclude all _ cvars as they are temporary + n = buf_getsize(h); + + adding = true; + pureadding = true; + + for(i = 0; i < n; ++i) + { + k = bufstr_get(h, i); + +#define BADPREFIX(p) if(substring(k, 0, strlen(p)) == p) continue +#define BADPRESUFFIX(p,s) if(substring(k, 0, strlen(p)) == p && substring(k, -strlen(s), -1) == s) continue +#define BADCVAR(p) if(k == p) continue + + // general excludes and namespaces for server admin used cvars + BADPREFIX("help_"); // PN's server has this listed as changed, let's not rat him out for THAT + + // internal + BADPREFIX("csqc_"); + BADPREFIX("cvar_check_"); + BADCVAR("gamecfg"); + BADCVAR("g_configversion"); + BADCVAR("halflifebsp"); + BADCVAR("sv_mapformat_is_quake2"); + BADCVAR("sv_mapformat_is_quake3"); + BADPREFIX("sv_world"); + + // client + BADPREFIX("chase_"); + BADPREFIX("cl_"); + BADPREFIX("con_"); + BADPREFIX("scoreboard_"); + BADPREFIX("g_campaign"); + BADPREFIX("g_waypointsprite_"); + BADPREFIX("gl_"); + BADPREFIX("joy"); + BADPREFIX("hud_"); + BADPREFIX("m_"); + BADPREFIX("menu_"); + BADPREFIX("net_slist_"); + BADPREFIX("r_"); + BADPREFIX("sbar_"); + BADPREFIX("scr_"); + BADPREFIX("snd_"); + BADPREFIX("show"); + BADPREFIX("sensitivity"); + BADPREFIX("userbind"); + BADPREFIX("v_"); + BADPREFIX("vid_"); + BADPREFIX("crosshair"); + BADCVAR("mod_q3bsp_lightmapmergepower"); + BADCVAR("mod_q3bsp_nolightmaps"); + BADCVAR("fov"); + BADCVAR("mastervolume"); + BADCVAR("volume"); + BADCVAR("bgmvolume"); + BADCVAR("in_pitch_min"); + BADCVAR("in_pitch_max"); + + // private + BADCVAR("developer"); + BADCVAR("log_dest_udp"); + BADCVAR("net_address"); + BADCVAR("net_address_ipv6"); + BADCVAR("port"); + BADCVAR("savedgamecfg"); + BADCVAR("serverconfig"); + BADCVAR("sv_autoscreenshot"); + BADCVAR("sv_heartbeatperiod"); + BADCVAR("sv_vote_master_password"); + BADCVAR("sys_colortranslation"); + BADCVAR("sys_specialcharactertranslation"); + BADCVAR("timeformat"); + BADCVAR("timestamps"); + BADCVAR("g_require_stats"); + BADPREFIX("developer_"); + BADPREFIX("g_ban_"); + BADPREFIX("g_banned_list"); + BADPREFIX("g_require_stats_"); + BADPREFIX("g_chat_flood_"); + BADPREFIX("g_ghost_items"); + BADPREFIX("g_playerstats_"); + BADPREFIX("g_voice_flood_"); + BADPREFIX("log_file"); + BADPREFIX("quit_"); + BADPREFIX("rcon_"); + BADPREFIX("sv_allowdownloads"); + BADPREFIX("sv_autodemo"); + BADPREFIX("sv_curl_"); + BADPREFIX("sv_eventlog"); + BADPREFIX("sv_logscores_"); + BADPREFIX("sv_master"); + BADPREFIX("sv_weaponstats_"); + BADPREFIX("sv_waypointsprite_"); + BADCVAR("rescan_pending"); + + // these can contain player IDs, so better hide + BADPREFIX("g_forced_team_"); + BADCVAR("sv_muteban_list"); + BADCVAR("sv_voteban_list"); + BADCVAR("sv_allow_customplayermodels_idlist"); + BADCVAR("sv_allow_customplayermodels_speciallist"); + + // mapinfo + BADCVAR("fraglimit"); + BADCVAR("g_arena"); + BADCVAR("g_assault"); + BADCVAR("g_ca"); + BADCVAR("g_ca_teams"); + BADCVAR("g_conquest"); + BADCVAR("g_conquest_teams"); + BADCVAR("g_ctf"); + BADCVAR("g_cts"); + BADCVAR("g_dotc"); + BADCVAR("g_dm"); + BADCVAR("g_domination"); + BADCVAR("g_domination_default_teams"); + BADCVAR("g_duel"); + BADCVAR("g_duel_not_dm_maps"); + BADCVAR("g_freezetag"); + BADCVAR("g_freezetag_teams"); + BADCVAR("g_invasion_teams"); + BADCVAR("g_invasion_type"); + BADCVAR("g_jailbreak"); + BADCVAR("g_jailbreak_teams"); + BADCVAR("g_keepaway"); + BADCVAR("g_keyhunt"); + BADCVAR("g_keyhunt_teams"); + BADCVAR("g_lms"); + BADCVAR("g_nexball"); + BADCVAR("g_onslaught"); + BADCVAR("g_race"); + BADCVAR("g_race_laps_limit"); + BADCVAR("g_race_qualifying_timelimit"); + BADCVAR("g_race_qualifying_timelimit_override"); + BADCVAR("g_runematch"); + BADCVAR("g_shootfromeye"); + BADCVAR("g_snafu"); + BADCVAR("g_survival"); + BADCVAR("g_survival_not_dm_maps"); + BADCVAR("g_tdm"); + BADCVAR("g_tdm_on_dm_maps"); + BADCVAR("g_tdm_teams"); + BADCVAR("g_vip"); + BADCVAR("leadlimit"); + BADCVAR("nextmap"); + BADCVAR("teamplay"); + BADCVAR("timelimit"); + BADCVAR("g_mapinfo_settemp_acl"); + BADCVAR("g_mapinfo_ignore_warnings"); + BADCVAR("g_maplist_ignore_sizes"); + BADCVAR("g_maplist_sizes_count_bots"); + + // long + BADCVAR("hostname"); + BADCVAR("g_maplist"); + BADCVAR("g_maplist_mostrecent"); + BADCVAR("sv_motd"); + + v = cvar_string(k); + d = cvar_defstring(k); + if(v == d) + continue; + + if(adding) + { + cvar_changes = strcat(cvar_changes, k, " \"", v, "\" // \"", d, "\"\n"); + if(strlen(cvar_changes) > 16384) + { + cvar_changes = "// too many settings have been changed to show them here\n"; + adding = 0; + } + } + + // now check if the changes are actually gameplay relevant + + // does nothing gameplay relevant + BADCVAR("captureleadlimit_override"); + BADCVAR("condump_stripcolors"); + BADCVAR("gameversion"); + BADCVAR("fs_gamedir"); + BADCVAR("g_allow_oldvortexbeam"); + BADCVAR("g_balance_kill_delay"); + BADCVAR("g_buffs_pickup_anyway"); + BADCVAR("g_buffs_randomize"); + BADCVAR("g_buffs_randomize_teamplay"); + BADCVAR("g_campcheck_distance"); + BADCVAR("g_chatsounds"); + BADCVAR("g_ca_point_leadlimit"); + BADCVAR("g_ca_point_limit"); + BADCVAR("g_ctf_captimerecord_always"); + BADCVAR("g_ctf_flag_glowtrails"); + BADCVAR("g_ctf_dynamiclights"); + BADCVAR("g_ctf_flag_pickup_verbosename"); + BADPRESUFFIX("g_ctf_flag_", "_model"); + BADPRESUFFIX("g_ctf_flag_", "_skin"); + BADCVAR("g_domination_point_leadlimit"); + BADCVAR("g_forced_respawn"); + BADCVAR("g_freezetag_point_leadlimit"); + BADCVAR("g_freezetag_point_limit"); + BADCVAR("g_glowtrails"); + BADCVAR("g_hats"); + BADCVAR("g_casings"); + BADCVAR("g_invasion_point_limit"); + BADCVAR("g_jump_grunt"); + BADCVAR("g_keepaway_ballcarrier_effects"); + BADCVAR("g_keepawayball_effects"); + BADCVAR("g_keyhunt_point_leadlimit"); + BADCVAR("g_nexball_goalleadlimit"); + BADCVAR("g_new_toys_autoreplace"); + BADCVAR("g_new_toys_use_pickupsound"); + BADCVAR("g_physics_predictall"); + BADCVAR("g_piggyback"); + BADCVAR("g_playerclip_collisions"); + BADCVAR("g_spawn_alloweffects"); + BADCVAR("g_tdm_point_leadlimit"); + BADCVAR("g_tdm_point_limit"); + BADCVAR("leadlimit_and_fraglimit"); + BADCVAR("leadlimit_override"); + BADCVAR("pausable"); + BADCVAR("sv_announcer"); + BADCVAR("sv_checkforpacketsduringsleep"); + BADCVAR("sv_damagetext"); + BADCVAR("sv_db_saveasdump"); + BADCVAR("sv_intermission_cdtrack"); + BADCVAR("sv_mapchange_delay"); + BADCVAR("sv_minigames"); + BADCVAR("sv_namechangetimer"); + BADCVAR("sv_precacheplayermodels"); + BADCVAR("sv_radio"); + BADCVAR("sv_stepheight"); + BADCVAR("sv_timeout"); + BADCVAR("sv_weapons_modeloverride"); + BADCVAR("w_prop_interval"); + BADPREFIX("chat_"); + BADPREFIX("crypto_"); + BADPREFIX("gameversion_"); + BADPREFIX("g_chat_"); + BADPREFIX("g_ctf_captimerecord_"); + BADPREFIX("g_hats_"); + BADPREFIX("g_maplist_"); + BADPREFIX("g_mod_"); + BADPREFIX("g_respawn_"); + BADPREFIX("net_"); + BADPREFIX("notification_"); + BADPREFIX("prvm_"); + BADPREFIX("skill_"); + BADPREFIX("sv_allow_"); + BADPREFIX("sv_cullentities_"); + BADPREFIX("sv_maxidle_"); + BADPREFIX("sv_minigames_"); + BADPREFIX("sv_radio_"); + BADPREFIX("sv_timeout_"); + BADPREFIX("sv_vote_"); + BADPREFIX("timelimit_"); + + // allowed changes to server admins (please sync this to server.cfg) + // vi commands: + // :/"impure"/,$d + // :g!,^\/\/[^ /],d + // :%s,//\([^ ]*\).*,BADCVAR("\1");, + // :%!sort + // yes, this does contain some redundant stuff, don't really care + BADPREFIX("bot_ai_"); + BADCVAR("bot_config_file"); + BADCVAR("bot_number"); + BADCVAR("bot_prefix"); + BADCVAR("bot_suffix"); + BADCVAR("capturelimit_override"); + BADCVAR("fraglimit_override"); + BADCVAR("gametype"); + BADCVAR("g_antilag"); + BADCVAR("g_balance_teams"); + BADCVAR("g_balance_teams_prevent_imbalance"); + BADCVAR("g_balance_teams_scorefactor"); + BADCVAR("g_ban_sync_trusted_servers"); + BADCVAR("g_ban_sync_uri"); + BADCVAR("g_buffs"); + BADCVAR("g_ca_teams_override"); + BADCVAR("g_ctf_fullbrightflags"); + BADCVAR("g_ctf_ignore_frags"); + BADCVAR("g_ctf_leaderboard"); + BADCVAR("g_domination_point_limit"); + BADCVAR("g_domination_teams_override"); + BADCVAR("g_freezetag_teams_override"); + BADCVAR("g_friendlyfire"); + BADCVAR("g_fullbrightitems"); + BADCVAR("g_fullbrightplayers"); + BADCVAR("g_keyhunt_point_limit"); + BADCVAR("g_keyhunt_teams_override"); + BADCVAR("g_lms_lives_override"); + BADCVAR("g_maplist"); + BADCVAR("g_maxplayers"); + BADCVAR("g_mirrordamage"); + BADCVAR("g_nexball_goallimit"); + BADCVAR("g_norecoil"); + BADCVAR("g_physics_clientselect"); + BADCVAR("g_pinata"); + BADCVAR("g_powerups"); + BADCVAR("g_player_brightness"); + BADCVAR("g_rocket_flying"); + BADCVAR("g_rocket_flying_disabledelays"); + BADCVAR("g_spawnshieldtime"); + BADCVAR("g_start_delay"); + BADCVAR("g_superspectate"); + BADCVAR("g_tdm_teams_override"); + BADCVAR("g_warmup"); + BADCVAR("g_weapon_stay"); BADPRESUFFIX("g_", "_weapon_stay"); + BADCVAR("hostname"); + BADCVAR("log_file"); + BADCVAR("maxplayers"); + BADCVAR("minplayers"); + BADCVAR("minplayers_per_team"); + BADCVAR("net_address"); + BADCVAR("port"); + BADCVAR("rcon_password"); + BADCVAR("rcon_restricted_commands"); + BADCVAR("rcon_restricted_password"); + BADCVAR("skill"); + BADCVAR("sv_adminnick"); + BADCVAR("sv_autoscreenshot"); + BADCVAR("sv_autotaunt"); + BADCVAR("sv_curl_defaulturl"); + BADCVAR("sv_defaultcharacter"); + BADCVAR("sv_defaultcharacterskin"); + BADCVAR("sv_defaultplayercolors"); + BADCVAR("sv_defaultplayermodel"); + BADCVAR("sv_defaultplayerskin"); + BADCVAR("sv_maxidle"); + BADCVAR("sv_maxrate"); + BADCVAR("sv_motd"); + BADCVAR("sv_public"); + BADCVAR("sv_ready_restart"); + BADCVAR("sv_status_privacy"); + BADCVAR("sv_taunt"); + BADCVAR("sv_vote_call"); + BADCVAR("sv_vote_commands"); + BADCVAR("sv_vote_majority_factor"); + BADCVAR("sv_vote_master"); + BADCVAR("sv_vote_master_commands"); + BADCVAR("sv_vote_master_password"); + BADCVAR("sv_vote_simple_majority_factor"); + BADCVAR("teamplay_mode"); + BADCVAR("timelimit_override"); + BADPREFIX("g_warmup_"); + BADPREFIX("sv_info_"); + BADPREFIX("sv_ready_restart_"); + + // mutators that announce themselves properly to the server browser + BADCVAR("g_instagib"); + BADCVAR("g_new_toys"); + BADCVAR("g_nix"); + BADCVAR("g_grappling_hook"); + BADCVAR("g_jetpack"); + + // temporary for testing + // TODO remove before 0.8.3 release + BADCVAR("g_ca_weaponarena"); + BADCVAR("g_freezetag_weaponarena"); + BADCVAR("g_lms_weaponarena"); + BADCVAR("g_ctf_stalemate_time"); + + if(cvar_string("g_mod_balance") == "Testing") + { + // (temporary) while using the Testing balance, any weapon balance cvars are allowed to be changed + BADPREFIX("g_balance_"); + } + +#undef BADPRESUFFIX +#undef BADPREFIX +#undef BADCVAR + + if(pureadding) + { + cvar_purechanges = strcat(cvar_purechanges, k, " \"", v, "\" // \"", d, "\"\n"); + if(strlen(cvar_purechanges) > 16384) + { + cvar_purechanges = "// too many settings have been changed to show them here\n"; + pureadding = 0; + } + } + ++cvar_purechanges_count; + // WARNING: this variable is used for the server list + // NEVER dare to skip this code! + // Hacks to intentionally appearing as "pure server" even though you DO have + // modified settings may be punished by removal from the server list. + // You can do to the variables cvar_changes and cvar_purechanges all you want, + // though. + } + buf_del(h); + if(cvar_changes == "") + cvar_changes = "// this server runs at default server settings\n"; + else + cvar_changes = strcat("// this server runs at modified server settings:\n", cvar_changes); + cvar_changes = strzone(cvar_changes); + if(cvar_purechanges == "") + cvar_purechanges = "// this server runs at default gameplay settings\n"; + else + cvar_purechanges = strcat("// this server runs at modified gameplay settings:\n", cvar_purechanges); + cvar_purechanges = strzone(cvar_purechanges); +} + +entity randomseed; +bool RandomSeed_Send(entity this, entity to, int sf) +{ + WriteHeader(MSG_ENTITY, ENT_CLIENT_RANDOMSEED); + WriteShort(MSG_ENTITY, this.cnt); + return true; +} +void RandomSeed_Think(entity this) +{ + this.cnt = bound(0, floor(random() * 65536), 65535); + this.nextthink = time + 5; + + this.SendFlags |= 1; +} +void RandomSeed_Spawn() +{ + randomseed = new_pure(randomseed); + setthink(randomseed, RandomSeed_Think); + Net_LinkEntity(randomseed, false, 0, RandomSeed_Send); + + getthink(randomseed)(randomseed); // sets random seed and nextthink +} + +spawnfunc(__init_dedicated_server) +{ + // handler for _init/_init map (only for dedicated server initialization) + + world_initialized = -1; // don't complain + + delete_fn = remove_unsafely; + + entity e = spawn(); + setthink(e, GotoFirstMap); + e.nextthink = time; // this is usually 1 at this point + + e = new(info_player_deathmatch); // safeguard against player joining + + // assign reflectively to avoid "assignment to world" warning + for (int i = 0, n = numentityfields(); i < n; ++i) { + string k = entityfieldname(i); + if (k == "classname") { + // safeguard against various stuff ;) + putentityfieldstring(i, this, "worldspawn"); + break; + } + } + + // needs to be done so early because of the constants they create + static_init(); + static_init_late(); + static_init_precache(); + + IL_PUSH(g_spawnpoints, e); // just incase + + MapInfo_Enumerate(); + MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags(), 0); +} + +void __init_dedicated_server_shutdown() { + MapInfo_Shutdown(); +} + +STATIC_INIT_EARLY(maxclients) +{ + maxclients = 0; + for (entity head = nextent(NULL); head; head = nextent(head)) { + ++maxclients; + } +} + +void default_delayedinit(entity this) +{ + if(!scores_initialized) + ScoreRules_generic(); +} + +void InitGameplayMode() +{ + VoteReset(); + + // find out good world mins/maxs bounds, either the static bounds found by looking for solid, or the mapinfo specified bounds + get_mi_min_max(1); + // assign reflectively to avoid "assignment to world" warning + int done = 0; for (int i = 0, n = numentityfields(); i < n; ++i) { + string k = entityfieldname(i); vector v = (k == "mins") ? mi_min : (k == "maxs") ? mi_max : '0 0 0'; + if (v) { + putentityfieldstring(i, world, sprintf("%v", v)); + if (++done == 2) break; + } + } + // currently, NetRadiant's limit is 131072 qu for each side + // distance from one corner of a 131072qu cube to the opposite corner is approx. 227023 qu + // set the distance according to map size but don't go over the limit to avoid issues with float precision + // in case somebody makes extremely large maps + max_shot_distance = min(230000, vlen(world.maxs - world.mins)); + + MapInfo_LoadMapSettings(mapname); + GameRules_teams(false); + + if (!cvar_value_issafe(world.fog)) + { + LOG_INFO("The current map contains a potentially harmful fog setting, ignored"); + world.fog = string_null; + } + if(MapInfo_Map_fog != "") + { + if(MapInfo_Map_fog == "none") + world.fog = string_null; + else + world.fog = strzone(MapInfo_Map_fog); + } + clientstuff = strzone(MapInfo_Map_clientstuff); + + MapInfo_ClearTemps(); + + gamemode_name = MapInfo_Type_ToText(MapInfo_LoadedGametype); + + cache_mutatormsg = strzone(""); + cache_lastmutatormsg = strzone(""); + + InitializeEntity(NULL, default_delayedinit, INITPRIO_GAMETYPE_FALLBACK); +} + +void Map_MarkAsRecent(string m); +float world_already_spawned; +spawnfunc(worldspawn) +{ + server_is_dedicated = boolean(stof(cvar_defstring("is_dedicated"))); + + bool wantrestart = false; + { + if (!server_is_dedicated) + { + // force unloading of server pk3 files when starting a listen server + // localcmd("\nfs_rescan\n"); // FIXME: does more harm than good, has unintended side effects. What we really want is to unload temporary pk3s only + // restore csqc_progname too + string expect = "csprogs.dat"; + wantrestart = cvar_string("csqc_progname") != expect; + cvar_set("csqc_progname", expect); + } + else + { + // Try to use versioned csprogs from pk3 + // Only ever use versioned csprogs.dat files on dedicated servers; + // we need to reset csqc_progname on clients ourselves, and it's easier if the client's release name is constant + string pk3csprogs = "csprogs-" WATERMARK ".dat"; + // This always works; fall back to it if a versioned csprogs.dat is suddenly missing + string select = "csprogs.dat"; + if (fexists(pk3csprogs)) select = pk3csprogs; + if (cvar_string("csqc_progname") != select) + { + cvar_set("csqc_progname", select); + wantrestart = true; + } + // Check for updates on startup + // We do it this way for atomicity so that connecting clients still match the server progs and don't disconnect + int sentinel = fopen("progs.txt", FILE_READ); + if (sentinel >= 0) + { + string switchversion = fgets(sentinel); + fclose(sentinel); + if (switchversion != "" && switchversion != WATERMARK) + { + LOG_INFOF("Switching progs: " WATERMARK " -> %s", switchversion); + // if it doesn't exist, assume either: + // a) the current program was overwritten + // b) this is a client only update + string newprogs = sprintf("progs-%s.dat", switchversion); + if (fexists(newprogs)) + { + cvar_set("sv_progs", newprogs); + wantrestart = true; + } + string newcsprogs = sprintf("csprogs-%s.dat", switchversion); + if (fexists(newcsprogs)) + { + cvar_set("csqc_progname", newcsprogs); + wantrestart = true; + } + } + } + } + if (wantrestart) + { + LOG_INFO("Restart requested"); + changelevel(mapname); + // let initialization continue, shutdown depends on it + } + } + + if(world_already_spawned) + error("world already spawned - you may have EXACTLY ONE worldspawn!"); + world_already_spawned = true; + + delete_fn = remove_safely; // during spawning, watch what you remove! + + cvar_changes_init(); // do this very early now so it REALLY matches the server config + + // needs to be done so early because of the constants they create + static_init(); + + ServerProgsDB = db_load(strcat("server.db", autocvar_sessionid)); + + TemporaryDB = db_create(); + + // 0 normal + lightstyle(0, "m"); + + // 1 FLICKER (first variety) + lightstyle(1, "mmnmmommommnonmmonqnmmo"); + + // 2 SLOW STRONG PULSE + lightstyle(2, "abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba"); + + // 3 CANDLE (first variety) + lightstyle(3, "mmmmmaaaaammmmmaaaaaabcdefgabcdefg"); + + // 4 FAST STROBE + lightstyle(4, "mamamamamama"); + + // 5 GENTLE PULSE 1 + lightstyle(5,"jklmnopqrstuvwxyzyxwvutsrqponmlkj"); + + // 6 FLICKER (second variety) + lightstyle(6, "nmonqnmomnmomomno"); + + // 7 CANDLE (second variety) + lightstyle(7, "mmmaaaabcdefgmmmmaaaammmaamm"); + + // 8 CANDLE (third variety) + lightstyle(8, "mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa"); + + // 9 SLOW STROBE (fourth variety) + lightstyle(9, "aaaaaaaazzzzzzzz"); + + // 10 FLUORESCENT FLICKER + lightstyle(10, "mmamammmmammamamaaamammma"); + + // 11 SLOW PULSE NOT FADE TO BLACK + lightstyle(11, "abcdefghijklmnopqrrqponmlkjihgfedcba"); + + // styles 32-62 are assigned by the spawnfunc_light program for switchable lights + + // 63 testing + lightstyle(63, "a"); + + if(autocvar_g_campaign) + CampaignPreInit(); + + Map_MarkAsRecent(mapname); + + PlayerStats_GameReport_Init(); // we need this to be initiated before InitGameplayMode + + InitGameplayMode(); + static_init_late(); + static_init_precache(); + readlevelcvars(); + GrappleHookInit(); + + GameRules_limit_fallbacks(); + + if(warmup_limit == 0) + warmup_limit = (autocvar_timelimit > 0) ? autocvar_timelimit * 60 : autocvar_timelimit; + + player_count = 0; + bot_waypoints_for_items = autocvar_g_waypoints_for_items; + if(bot_waypoints_for_items == 1) + if(this.spawnflags & SPAWNFLAG_NO_WAYPOINTS_FOR_ITEMS) + bot_waypoints_for_items = 0; + + WaypointSprite_Init(); + + GameLogInit(); // prepare everything + // NOTE for matchid: + // changing the logic generating it is okay. But: + // it HAS to stay <= 64 chars + // character set: ASCII 33-126 without the following characters: : ; ' " \ $ + if(autocvar_sv_eventlog) + { + string s = sprintf("%s.%s.%06d", itos(autocvar_sv_eventlog_files_counter), strftime(false, "%s"), floor(random() * 1000000)); + matchid = strzone(s); + + GameLogEcho(strcat(":gamestart:", GetGametype(), "_", GetMapname(), ":", s)); + s = ":gameinfo:mutators:LIST"; + + MUTATOR_CALLHOOK(BuildMutatorsString, s); + s = M_ARGV(0, string); + + // initialiation stuff, not good in the mutator system + if(!autocvar_g_use_ammunition) + s = strcat(s, ":no_use_ammunition"); + + // initialiation stuff, not good in the mutator system + if(autocvar_g_pickup_items == 0) + s = strcat(s, ":no_pickup_items"); + if(autocvar_g_pickup_items > 0) + s = strcat(s, ":pickup_items"); + + // initialiation stuff, not good in the mutator system + if(autocvar_g_weaponarena != "0") + s = strcat(s, ":", autocvar_g_weaponarena, " arena"); + + // TODO to mutator system + if(autocvar_g_norecoil) + s = strcat(s, ":norecoil"); + + // TODO to mutator system + if(autocvar_g_powerups == 0) + s = strcat(s, ":no_powerups"); + if(autocvar_g_powerups > 0) + s = strcat(s, ":powerups"); + + GameLogEcho(s); + GameLogEcho(":gameinfo:end"); + } + else + matchid = strzone(ftos(random())); + + cvar_set("nextmap", ""); + + SetDefaultAlpha(); + + if(autocvar_g_campaign) + CampaignPostInit(); + + Ban_LoadBans(); + + MapInfo_Enumerate(); + MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags(), 1); + + if(fexists(strcat("scripts/", mapname, ".arena"))) + cvar_settemp("sv_q3acompat_machineshotgunswap", "1"); + + if(fexists(strcat("scripts/", mapname, ".defi"))) + cvar_settemp("sv_q3defragcompat", "1"); + + if(whichpack(strcat("maps/", mapname, ".cfg")) != "") + { + int fd = fopen(strcat("maps/", mapname, ".cfg"), FILE_READ); + if(fd != -1) + { + string s; + while((s = fgets(fd))) + { + int l = tokenize_console(s); + if(l < 2) + continue; + if(argv(0) == "cd") + { + string trackname = argv(2); + LOG_INFO("Found ^1UNSUPPORTED^7 cd loop command in .cfg file; put this line in mapinfo instead:"); + LOG_INFO(" cdtrack ", trackname); + if (cvar_value_issafe(trackname)) + { + string newstuff = strcat(clientstuff, "cd loop \"", trackname, "\"\n"); + strcpy(clientstuff, newstuff); + } + } + else if(argv(0) == "fog") + { + LOG_INFO("Found ^1UNSUPPORTED^7 fog command in .cfg file; put this line in worldspawn in the .map/.bsp/.ent file instead:"); + LOG_INFO(" \"fog\" \"", s, "\""); + } + else if(argv(0) == "set") + { + LOG_INFO("Found ^1UNSUPPORTED^7 set command in .cfg file; put this line in mapinfo instead:"); + LOG_INFO(" clientsettemp_for_type all ", argv(1), " ", argv(2)); + } + else if(argv(0) != "//") + { + LOG_INFO("Found ^1UNSUPPORTED^7 set command in .cfg file; put this line in mapinfo instead:"); + LOG_INFO(" clientsettemp_for_type all ", argv(0), " ", argv(1)); + } + } + fclose(fd); + } + } + + WeaponStats_Init(); + + Nagger_Init(); + + // set up information replies for clients and server to use + maplist_reply = strzone(getmaplist()); + lsmaps_reply = strzone(getlsmaps()); + monsterlist_reply = strzone(getmonsterlist()); + for(int i = 0; i < 10; ++i) + { + string s = getrecords(i); + if (s) + records_reply[i] = strzone(s); + } + ladder_reply = strzone(getladder()); + rankings_reply = strzone(getrankings()); + + // begin other init + ClientInit_Spawn(); + RandomSeed_Spawn(); + PingPLReport_Spawn(); + + CheatInit(); + + if (!wantrestart) localcmd("\n_sv_hook_gamestart ", GetGametype(), "\n"); + + // fill sv_curl_serverpackages from .serverpackage files + if (autocvar_sv_curl_serverpackages_auto) + { + string s = "csprogs-" WATERMARK ".txt"; + // remove automatically managed files from the list to prevent duplicates + for (int i = 0, n = tokenize_console(cvar_string("sv_curl_serverpackages")); i < n; ++i) + { + string pkg = argv(i); + if (startsWith(pkg, "csprogs-")) continue; + if (endsWith(pkg, "-serverpackage.txt")) continue; + if (endsWith(pkg, ".serverpackage")) continue; // OLD legacy + s = cons(s, pkg); + } + // add automatically managed files to the list + #define X(match) MACRO_BEGIN \ + int fd = search_begin(match, true, false); \ + if (fd >= 0) \ + { \ + for (int i = 0, j = search_getsize(fd); i < j; ++i) \ + { \ + s = cons(s, search_getfilename(fd, i)); \ + } \ + search_end(fd); \ + } \ + MACRO_END + X("*-serverpackage.txt"); + X("*.serverpackage"); + #undef X + cvar_set("sv_curl_serverpackages", s); + } + + // MOD AUTHORS: change this, and possibly remove a few of the blocks below to ignore certain changes + modname = "Xonotic"; + // physics/balance/config changes that count as mod + if(cvar_string("g_mod_physics") != cvar_defstring("g_mod_physics")) + modname = cvar_string("g_mod_physics"); + if(cvar_string("g_mod_balance") != cvar_defstring("g_mod_balance") && cvar_string("g_mod_balance") != "Testing") + modname = cvar_string("g_mod_balance"); + if(cvar_string("g_mod_config") != cvar_defstring("g_mod_config")) + modname = cvar_string("g_mod_config"); + // extra mutators that deserve to count as mod + MUTATOR_CALLHOOK(SetModname, modname); + modname = M_ARGV(0, string); + + // save it for later + modname = strzone(modname); + + WinningConditionHelper(this); // set worldstatus + + world_initialized = 1; + __spawnfunc_spawn_all(); +} + +spawnfunc(light) +{ + //makestatic (this); // Who the f___ did that? + delete(this); +} + +string GetGametype() +{ + return MapInfo_Type_ToString(MapInfo_LoadedGametype); +} + +string GetMapname() +{ + return mapname; +} + +float Map_Count, Map_Current; +string Map_Current_Name; + +// NOTE: this now expects the map list to be already tokenized and the count in Map_Count +int GetMaplistPosition() +{ + string map = GetMapname(); + int idx = autocvar_g_maplist_index; + + if(idx >= 0) + { + if(idx < Map_Count) + { + if(map == argv(idx)) + { + return idx; + } + } + } + + for(int pos = 0; pos < Map_Count; ++pos) + { + if(map == argv(pos)) + return pos; + } + + // resume normal maplist rotation if current map is not in g_maplist + return idx; +} + +bool MapHasRightSize(string map) +{ + int minplayers = max(0, floor(autocvar_minplayers)); + if (teamplay) + minplayers = max(0, floor(autocvar_minplayers_per_team) * AvailableTeams()); + if (autocvar_g_maplist_check_waypoints + && (currentbots || autocvar_bot_number || player_count < minplayers)) + { + string checkwp_msg = strcat("checkwp ", map); + if(!fexists(strcat("maps/", map, ".waypoints"))) + { + LOG_TRACE(checkwp_msg, ": no waypoints"); + return false; + } + LOG_TRACE(checkwp_msg, ": has waypoints"); + } + + if(autocvar_g_maplist_ignore_sizes) + return true; + + // open map size restriction file + string opensize_msg = strcat("opensize ", map); + float fh = fopen(strcat("maps/", map, ".sizes"), FILE_READ); + int player_limit = ((autocvar_g_maplist_sizes_count_maxplayers) ? GetPlayerLimit() : 0); + int pcount = ((player_limit > 0) ? min(player_count, player_limit) : player_count); // bind it to the player limit so that forced spectators don't influence the limits + if(!autocvar_g_maplist_sizes_count_bots) + pcount -= currentbots; + if(fh >= 0) + { + opensize_msg = strcat(opensize_msg, ": ok, "); + int mapmin = stoi(fgets(fh)); + int mapmax = stoi(fgets(fh)); + fclose(fh); + if(pcount < mapmin) + { + LOG_TRACE(opensize_msg, "not enough"); + return false; + } + if(mapmax && pcount > mapmax) + { + LOG_TRACE(opensize_msg, "too many"); + return false; + } + LOG_TRACE(opensize_msg, "right size"); + return true; + } + LOG_TRACE(opensize_msg, ": not found"); + return true; +} + +string Map_Filename(float position) +{ + return strcat("maps/", argv(position), ".bsp"); +} + +void Map_MarkAsRecent(string m) +{ + cvar_set("g_maplist_mostrecent", strwords(cons(m, autocvar_g_maplist_mostrecent), max(0, autocvar_g_maplist_mostrecent_count))); +} + +float Map_IsRecent(string m) +{ + return strhasword(autocvar_g_maplist_mostrecent, m); +} + +float Map_Check(float position, float pass) +{ + string filename; + string map_next; + map_next = argv(position); + if(pass <= 1) + { + if(Map_IsRecent(map_next)) + return 0; + } + filename = Map_Filename(position); + if(MapInfo_CheckMap(map_next)) + { + if(pass == 2) + return 1; + if(MapHasRightSize(map_next)) + return 1; + return 0; + } + else + LOG_DEBUG( "Couldn't select '", filename, "'..." ); + + return 0; +} + +void Map_Goto_SetStr(string nextmapname) +{ + if(getmapname_stored != "") + strunzone(getmapname_stored); + if(nextmapname == "") + getmapname_stored = ""; + else + getmapname_stored = strzone(nextmapname); +} + +void Map_Goto_SetFloat(float position) +{ + cvar_set("g_maplist_index", ftos(position)); + Map_Goto_SetStr(argv(position)); +} + +void Map_Goto(float reinit) +{ + MapInfo_LoadMap(getmapname_stored, reinit); +} + +// return codes of map selectors: +// -1 = temporary failure (that is, try some method that is guaranteed to succeed) +// -2 = permanent failure +float MaplistMethod_Iterate() // usual method +{ + float pass, i; + + LOG_TRACE("Trying MaplistMethod_Iterate"); + + for(pass = 1; pass <= 2; ++pass) + { + for(i = 1; i < Map_Count; ++i) + { + float mapindex; + mapindex = (i + Map_Current) % Map_Count; + if(Map_Check(mapindex, pass)) + return mapindex; + } + } + return -1; +} + +float MaplistMethod_Repeat() // fallback method +{ + LOG_TRACE("Trying MaplistMethod_Repeat"); + + if(Map_Check(Map_Current, 2)) + return Map_Current; + return -2; +} + +float MaplistMethod_Random() // random map selection +{ + float i, imax; + + LOG_TRACE("Trying MaplistMethod_Random"); + + imax = 42; + + for(i = 0; i <= imax; ++i) + { + float mapindex; + mapindex = (Map_Current + floor(random() * (Map_Count - 1) + 1)) % Map_Count; // any OTHER map + if(Map_Check(mapindex, 1)) + return mapindex; + } + return -1; +} + +float MaplistMethod_Shuffle(float exponent) // more clever shuffling +// the exponent sets a bias on the map selection: +// the higher the exponent, the less likely "shortly repeated" same maps are +{ + float i, j, imax, insertpos; + + LOG_TRACE("Trying MaplistMethod_Shuffle"); + + imax = 42; + + for(i = 0; i <= imax; ++i) + { + string newlist; + + // now reinsert this at another position + insertpos = (random() ** (1 / exponent)); // ]0, 1] + insertpos = insertpos * (Map_Count - 1); // ]0, Map_Count - 1] + insertpos = ceil(insertpos) + 1; // {2, 3, 4, ..., Map_Count} + LOG_TRACE("SHUFFLE: insert pos = ", ftos(insertpos)); + + // insert the current map there + newlist = ""; + for(j = 1; j < insertpos; ++j) // i == 1: no loop, will be inserted as first; however, i == 1 has been excluded above + newlist = strcat(newlist, " ", argv(j)); + newlist = strcat(newlist, " ", argv(0)); // now insert the just selected map + for(j = insertpos; j < Map_Count; ++j) // i == Map_Count: no loop, has just been inserted as last + newlist = strcat(newlist, " ", argv(j)); + newlist = substring(newlist, 1, strlen(newlist) - 1); + cvar_set("g_maplist", newlist); + Map_Count = tokenizebyseparator(autocvar_g_maplist, " "); + + // NOTE: the selected map has just been inserted at (insertpos-1)th position + Map_Current = insertpos - 1; // this is not really valid, but this way the fallback has a chance of working + if(Map_Check(Map_Current, 1)) + return Map_Current; + } + return -1; +} + +void Maplist_Init() +{ + float i = Map_Count = 0; + if(autocvar_g_maplist != "") + { + Map_Count = tokenizebyseparator(autocvar_g_maplist, " "); + for (i = 0; i < Map_Count; ++i) + { + if (Map_Check(i, 2)) + break; + } + } + + if (i == Map_Count) + { + bprint( "Maplist contains no usable maps! Resetting it to default map list.\n" ); + cvar_set("g_maplist", MapInfo_ListAllAllowedMaps(MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags() | MAPINFO_FLAG_NOAUTOMAPLIST)); + if(autocvar_g_maplist_shuffle) + ShuffleMaplist(); + if(!server_is_dedicated) + localcmd("\nmenu_cmd sync\n"); + Map_Count = tokenizebyseparator(autocvar_g_maplist, " "); + } + if(Map_Count == 0) + error("empty maplist, cannot select a new map"); + Map_Current = bound(0, GetMaplistPosition(), Map_Count - 1); + + strcpy(Map_Current_Name, argv(Map_Current)); // will be automatically freed on exit thanks to DP + // this may or may not be correct, but who cares, in the worst case a map + // isn't chosen in the first pass that should have been +} + +string GetNextMap() +{ + Maplist_Init(); + float nextMap = -1; + + if(nextMap == -1) + if(autocvar_g_maplist_shuffle > 0) + nextMap = MaplistMethod_Shuffle(autocvar_g_maplist_shuffle + 1); + + if(nextMap == -1) + if(autocvar_g_maplist_selectrandom) + nextMap = MaplistMethod_Random(); + + if(nextMap == -1) + nextMap = MaplistMethod_Iterate(); + + if(nextMap == -1) + nextMap = MaplistMethod_Repeat(); + + if(nextMap >= 0) + { + Map_Goto_SetFloat(nextMap); + return getmapname_stored; + } + + return ""; +} + +float DoNextMapOverride(float reinit) +{ + if(autocvar_g_campaign) + { + CampaignPostIntermission(); + alreadychangedlevel = true; + return true; + } + if(autocvar_quit_when_empty) + { + if(player_count <= currentbots) + { + localcmd("quit\n"); + alreadychangedlevel = true; + return true; + } + } + if(autocvar_quit_and_redirect != "") + { + redirection_target = strzone(autocvar_quit_and_redirect); + alreadychangedlevel = true; + return true; + } + if (!reinit && autocvar_samelevel) // if samelevel is set, stay on same level + { + localcmd("restart\n"); + alreadychangedlevel = true; + return true; + } + if(autocvar_nextmap != "") + { + string m; + m = GameTypeVote_MapInfo_FixName(autocvar_nextmap); + cvar_set("nextmap",m); + + if(!m || gametypevote) + return false; + if(autocvar_sv_vote_gametype) + { + Map_Goto_SetStr(m); + return false; + } + + if(MapInfo_CheckMap(m)) + { + Map_Goto_SetStr(m); + Map_Goto(reinit); + alreadychangedlevel = true; + return true; + } + } + if(!reinit && autocvar_lastlevel) + { + cvar_settemp_restore(); + localcmd("set lastlevel 0\ntogglemenu 1\n"); + alreadychangedlevel = true; + return true; + } + return false; +} + +void GotoNextMap(float reinit) +{ + //string nextmap; + //float n, nummaps; + //string s; + if (alreadychangedlevel) + return; + alreadychangedlevel = true; + + string nextMap = GetNextMap(); + if(nextMap == "") + error("Everything is broken - cannot find a next map. Please report this to the developers."); + Map_Goto(reinit); +} + + +/* +============ +IntermissionThink + +When the player presses attack or jump, change to the next level +============ +*/ +.float autoscreenshot; +void IntermissionThink(entity this) +{ + FixIntermissionClient(this); + + float server_screenshot = (autocvar_sv_autoscreenshot && CS(this).cvar_cl_autoscreenshot); + float client_screenshot = (CS(this).cvar_cl_autoscreenshot == 2); + + if( (server_screenshot || client_screenshot) + && ((this.autoscreenshot > 0) && (time > this.autoscreenshot)) ) + { + this.autoscreenshot = -1; + if(IS_REAL_CLIENT(this)) { stuffcmd(this, sprintf("\nscreenshot screenshots/autoscreenshot/%s-%s.jpg; echo \"^5A screenshot has been taken at request of the server.\"\n", GetMapname(), strftime(false, "%s"))); } + return; + } + + if (time < intermission_exittime) + return; + + if(!mapvote_initialized) + if (time < intermission_exittime + 10 && !(PHYS_INPUT_BUTTON_ATCK(this) || PHYS_INPUT_BUTTON_JUMP(this) || PHYS_INPUT_BUTTON_ATCK2(this) || PHYS_INPUT_BUTTON_HOOK(this) || PHYS_INPUT_BUTTON_USE(this))) + return; + + MapVote_Start(); +} + +/* +=============================================================================== + +RULES + +=============================================================================== +*/ + +void DumpStats(float final) +{ + float file; + string s; + float to_console; + float to_eventlog; + float to_file; + float i; + + to_console = autocvar_sv_logscores_console; + to_eventlog = autocvar_sv_eventlog; + to_file = autocvar_sv_logscores_file; + + if(!final) + { + to_console = true; // always print printstats replies + to_eventlog = false; // but never print them to the event log + } + + if(to_eventlog) + if(autocvar_sv_eventlog_console) + to_console = false; // otherwise we get the output twice + + if(final) + s = ":scores:"; + else + s = ":status:"; + s = strcat(s, GetGametype(), "_", GetMapname(), ":", ftos(rint(time))); + + if(to_console) + LOG_INFO(s); + if(to_eventlog) + GameLogEcho(s); + + file = -1; + if(to_file) + { + file = fopen(autocvar_sv_logscores_filename, FILE_APPEND); + if(file == -1) + to_file = false; + else + fputs(file, strcat(s, "\n")); + } + + s = strcat(":labels:player:", GetPlayerScoreString(NULL, 0)); + if(to_console) + LOG_INFO(s); + if(to_eventlog) + GameLogEcho(s); + if(to_file) + fputs(file, strcat(s, "\n")); + + FOREACH_CLIENT(IS_REAL_CLIENT(it) || (IS_BOT_CLIENT(it) && autocvar_sv_logscores_bots), { + s = strcat(":player:see-labels:", GetPlayerScoreString(it, 0), ":"); + s = strcat(s, ftos(rint(time - CS(it).jointime)), ":"); + if(IS_PLAYER(it) || MUTATOR_CALLHOOK(GetPlayerStatus, it)) + s = strcat(s, ftos(it.team), ":"); + else + s = strcat(s, "spectator:"); + + if(to_console) + LOG_INFO(s, playername(it, false)); + if(to_eventlog) + GameLogEcho(strcat(s, ftos(it.playerid), ":", playername(it, false))); + if(to_file) + fputs(file, strcat(s, playername(it, false), "\n")); + }); + + if(teamplay) + { + s = strcat(":labels:teamscores:", GetTeamScoreString(0, 0)); + if(to_console) + LOG_INFO(s); + if(to_eventlog) + GameLogEcho(s); + if(to_file) + fputs(file, strcat(s, "\n")); + + for(i = 1; i < 16; ++i) + { + s = strcat(":teamscores:see-labels:", GetTeamScoreString(i, 0)); + s = strcat(s, ":", ftos(i)); + if(to_console) + LOG_INFO(s); + if(to_eventlog) + GameLogEcho(s); + if(to_file) + fputs(file, strcat(s, "\n")); + } + } + + if(to_console) + LOG_INFO(":end"); + if(to_eventlog) + GameLogEcho(":end"); + if(to_file) + { + fputs(file, ":end\n"); + fclose(file); + } +} + +void FixIntermissionClient(entity e) +{ + if(!e.autoscreenshot) // initial call + { + e.autoscreenshot = time + 0.8; // used for autoscreenshot + SetResourceExplicit(e, RES_HEALTH, -2342); + // first intermission phase; voting phase has positive health (used to decide whether to send SVC_FINALE or not) + for (int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) + { + .entity weaponentity = weaponentities[slot]; + if(e.(weaponentity)) + { + e.(weaponentity).effects = EF_NODRAW; + if (e.(weaponentity).weaponchild) + e.(weaponentity).weaponchild.effects = EF_NODRAW; + } + } + if(IS_REAL_CLIENT(e)) + { + stuffcmd(e, "\nscr_printspeed 1000000\n"); + RandomSelection_Init(); + FOREACH_WORD(autocvar_sv_intermission_cdtrack, true, { + RandomSelection_AddString(it, 1, 1); + }); + if (RandomSelection_chosen_string != "") + { + stuffcmd(e, sprintf("\ncd loop %s\n", RandomSelection_chosen_string)); + } + msg_entity = e; + WriteByte(MSG_ONE, SVC_INTERMISSION); + } + } +} + +/* +go to the next level for deathmatch +only called if a time or frag limit has expired +*/ +void NextLevel() +{ + game_stopped = true; + intermission_running = 1; // game over + + // enforce a wait time before allowing changelevel + if(player_count > 0) + intermission_exittime = time + autocvar_sv_mapchange_delay; + else + intermission_exittime = -1; + + /* + WriteByte (MSG_ALL, SVC_CDTRACK); + WriteByte (MSG_ALL, 3); + WriteByte (MSG_ALL, 3); + // done in FixIntermission + */ + + //pos = FindIntermission (); + + VoteReset(); + + DumpStats(true); + + // send statistics + PlayerStats_GameReport(true); + WeaponStats_Shutdown(); + + Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_Null); // kill all centerprints now + + if(autocvar_sv_eventlog) + GameLogEcho(":gameover"); + + GameLogClose(); + + FOREACH_CLIENT(IS_PLAYER(it), { + FixIntermissionClient(it); + if(it.winning) + bprint(playername(it, false), " ^7wins.\n"); + }); + + target_music_kill(); + + if(autocvar_g_campaign) + CampaignPreIntermission(); + + MUTATOR_CALLHOOK(MatchEnd); + + localcmd("\nsv_hook_gameend\n"); +} + + +float InitiateSuddenDeath() +{ + // Check first whether normal overtimes could be added before initiating suddendeath mode + // - for this timelimit_overtime needs to be >0 of course + // - also check the winning condition calculated in the previous frame and only add normal overtime + // again, if at the point at which timelimit would be extended again, still no winner was found + if (!autocvar_g_campaign && checkrules_overtimesadded >= 0 + && (checkrules_overtimesadded < autocvar_timelimit_overtimes || autocvar_timelimit_overtimes < 0) + && autocvar_timelimit_overtime && !(g_race && !g_race_qualifying)) + { + return 1; // need to call InitiateOvertime later + } + else + { + if(!checkrules_suddendeathend) + { + if(autocvar_g_campaign) + checkrules_suddendeathend = time; // no suddendeath in campaign + else + checkrules_suddendeathend = time + 60 * autocvar_timelimit_suddendeath; + if(g_race && !g_race_qualifying) + race_StartCompleting(); + } + return 0; + } +} + +void InitiateOvertime() // ONLY call this if InitiateSuddenDeath returned true +{ + ++checkrules_overtimesadded; + //add one more overtime by simply extending the timelimit + cvar_set("timelimit", ftos(autocvar_timelimit + autocvar_timelimit_overtime)); + Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_OVERTIME_TIME, autocvar_timelimit_overtime * 60); +} + +float GetWinningCode(float fraglimitreached, float equality) +{ + if(autocvar_g_campaign == 1) + { + if(fraglimitreached) + return WINNING_YES; + else + return WINNING_NO; + } + else + { + if(equality) + { + if(fraglimitreached) + return WINNING_STARTSUDDENDEATHOVERTIME; + else + return WINNING_NEVER; + } + else + { + if(fraglimitreached) + return WINNING_YES; + else + return WINNING_NO; + } + } +} + +// set the .winning flag for exactly those players with a given field value +void SetWinners(.float field, float value) +{ + FOREACH_CLIENT(IS_PLAYER(it), { it.winning = (it.(field) == value); }); +} + +// set the .winning flag for those players with a given field value +void AddWinners(.float field, float value) +{ + FOREACH_CLIENT(IS_PLAYER(it), { + if(it.(field) == value) + it.winning = 1; + }); +} + +// clear the .winning flags +void ClearWinners() +{ + FOREACH_CLIENT(IS_PLAYER(it), { it.winning = 0; }); +} + +void ShuffleMaplist() +{ + cvar_set("g_maplist", shufflewords(autocvar_g_maplist)); +} + +int fragsleft_last; +float WinningCondition_Scores(float limit, float leadlimit) +{ + // TODO make everything use THIS winning condition (except LMS) + WinningConditionHelper(NULL); + + if(teamplay) + { + for (int i = 1; i < 5; ++i) + { + Team_SetTeamScore(Team_GetTeamFromIndex(i), + TeamScore_GetCompareValue(Team_IndexToTeam(i))); + } + } + + ClearWinners(); + if(WinningConditionHelper_winner) + WinningConditionHelper_winner.winning = 1; + if(WinningConditionHelper_winnerteam >= 0) + SetWinners(team, WinningConditionHelper_winnerteam); + + if(WinningConditionHelper_lowerisbetter) + { + WinningConditionHelper_topscore = -WinningConditionHelper_topscore; + WinningConditionHelper_secondscore = -WinningConditionHelper_secondscore; + limit = -limit; + } + + if(WinningConditionHelper_zeroisworst) + leadlimit = 0; // not supported in this mode + + if(MUTATOR_CALLHOOK(Scores_CountFragsRemaining)) + { + float fragsleft; + if (checkrules_suddendeathend && time >= checkrules_suddendeathend) + { + fragsleft = 1; + } + else + { + fragsleft = FLOAT_MAX; + float leadingfragsleft = FLOAT_MAX; + if (limit) + fragsleft = limit - WinningConditionHelper_topscore; + if (leadlimit) + leadingfragsleft = WinningConditionHelper_secondscore + leadlimit - WinningConditionHelper_topscore; + + if (limit && leadlimit && autocvar_leadlimit_and_fraglimit) + fragsleft = max(fragsleft, leadingfragsleft); + else + fragsleft = min(fragsleft, leadingfragsleft); + } + + if (fragsleft_last != fragsleft) // do not announce same remaining frags multiple times + { + if (fragsleft == 1) + Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_REMAINING_FRAG_1); + else if (fragsleft == 2) + Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_REMAINING_FRAG_2); + else if (fragsleft == 3) + Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_REMAINING_FRAG_3); + + fragsleft_last = fragsleft; + } + } + + bool fraglimit_reached = (limit && WinningConditionHelper_topscore >= limit); + bool leadlimit_reached = (leadlimit && WinningConditionHelper_topscore - WinningConditionHelper_secondscore >= leadlimit); + + bool limit_reached; + // only respect leadlimit_and_fraglimit when both limits are set or the game will never end + if (limit && leadlimit && autocvar_leadlimit_and_fraglimit) + limit_reached = (fraglimit_reached && leadlimit_reached); + else + limit_reached = (fraglimit_reached || leadlimit_reached); + + return GetWinningCode( + WinningConditionHelper_topscore && limit_reached, + WinningConditionHelper_equality + ); +} + +float WinningCondition_RanOutOfSpawns() +{ + if(have_team_spawns <= 0) + return WINNING_NO; + + if(!autocvar_g_spawn_useallspawns) + return WINNING_NO; + + if(!some_spawn_has_been_used) + return WINNING_NO; + + for (int i = 1; i < 5; ++i) + { + Team_SetTeamScore(Team_GetTeamFromIndex(i), 0); + } + + FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), + { + if (Team_IsValidTeam(it.team)) + { + Team_SetTeamScore(Team_GetTeam(it.team), 1); + } + }); + + IL_EACH(g_spawnpoints, true, + { + if (Team_IsValidTeam(it.team)) + { + Team_SetTeamScore(Team_GetTeam(it.team), 1); + } + }); + + ClearWinners(); + float team1_score = Team_GetTeamScore(Team_GetTeamFromIndex(1)); + float team2_score = Team_GetTeamScore(Team_GetTeamFromIndex(2)); + float team3_score = Team_GetTeamScore(Team_GetTeamFromIndex(3)); + float team4_score = Team_GetTeamScore(Team_GetTeamFromIndex(4)); + if(team1_score + team2_score + team3_score + team4_score == 0) + { + checkrules_equality = true; + return WINNING_YES; + } + else if(team1_score + team2_score + team3_score + team4_score == 1) + { + float t, i; + if(team1_score) + t = 1; + else if(team2_score) + t = 2; + else if(team3_score) + t = 3; + else // if(team4_score) + t = 4; + entity balance = TeamBalance_CheckAllowedTeams(NULL); + for(i = 0; i < MAX_TEAMSCORE; ++i) + { + for (int j = 1; j <= NUM_TEAMS; ++j) + { + if (t == j) + { + continue; + } + if (!TeamBalance_IsTeamAllowed(balance, j)) + { + continue; + } + TeamScore_AddToTeam(Team_IndexToTeam(j), i, -1000); + } + } + + AddWinners(team, t); + return WINNING_YES; + } + else + return WINNING_NO; +} + +/* +============ +CheckRules_World + +Exit deathmatch games upon conditions +============ +*/ +void CheckRules_World() +{ + VoteThink(); + MapVote_Think(); + + SetDefaultAlpha(); + + if (intermission_running) // someone else quit the game already + { + if(player_count == 0) // Nobody there? Then let's go to the next map + MapVote_Start(); + // this will actually check the player count in the next frame + // again, but this shouldn't hurt + return; + } + + float timelimit = autocvar_timelimit * 60; + float fraglimit = autocvar_fraglimit; + float leadlimit = autocvar_leadlimit; + if (leadlimit < 0) leadlimit = 0; + + if(warmup_stage || time <= game_starttime) // NOTE: this is <= to prevent problems in the very tic where the game starts + { + if(timelimit > 0) + timelimit = 0; // timelimit is not made for warmup + if(fraglimit > 0) + fraglimit = 0; // no fraglimit for now + leadlimit = 0; // no leadlimit for now + } + + if(timelimit > 0) + { + timelimit += game_starttime; + } + else if (timelimit < 0) + { + // endmatch + NextLevel(); + return; + } + + float wantovertime; + wantovertime = 0; + + if(checkrules_suddendeathend) + { + if(!checkrules_suddendeathwarning) + { + checkrules_suddendeathwarning = true; + if(g_race && !g_race_qualifying) + Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_RACE_FINISHLAP); + else + Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_OVERTIME_FRAG); + } + } + else + { + if (timelimit && time >= timelimit) + { + if(g_race && (g_race_qualifying == 2) && timelimit > 0) + { + float totalplayers; + float playerswithlaps; + float readyplayers; + totalplayers = playerswithlaps = readyplayers = 0; + FOREACH_CLIENT(IS_PLAYER(it), { + ++totalplayers; + if(GameRules_scoring_add(it, RACE_FASTEST, 0)) + ++playerswithlaps; + if(it.ready) + ++readyplayers; + }); + + // at least 2 of the players have completed a lap: start the RACE + // otherwise, the players should end the qualifying on their own + if(readyplayers || playerswithlaps >= 2) + { + checkrules_suddendeathend = 0; + ReadyRestart(); // go to race + return; + } + else + wantovertime |= InitiateSuddenDeath(); + } + else + wantovertime |= InitiateSuddenDeath(); + } + } + + if (checkrules_suddendeathend && time >= checkrules_suddendeathend) + { + NextLevel(); + return; + } + + int checkrules_status = WinningCondition_RanOutOfSpawns(); + if(checkrules_status == WINNING_YES) + bprint("Hey! Someone ran out of spawns!\n"); + else if(MUTATOR_CALLHOOK(CheckRules_World, checkrules_status, timelimit, fraglimit)) + checkrules_status = M_ARGV(0, float); + else + checkrules_status = WinningCondition_Scores(fraglimit, leadlimit); + + if(checkrules_status == WINNING_STARTSUDDENDEATHOVERTIME) + { + checkrules_status = WINNING_NEVER; + checkrules_overtimesadded = -1; + wantovertime |= InitiateSuddenDeath(); + } + + if(checkrules_status == WINNING_NEVER) + // equality cases! Nobody wins if the overtime ends in a draw. + ClearWinners(); + + if(wantovertime) + { + if(checkrules_status == WINNING_NEVER) + InitiateOvertime(); + else + checkrules_status = WINNING_YES; + } + + if(checkrules_suddendeathend) + if(checkrules_status != WINNING_NEVER || time >= checkrules_suddendeathend) + checkrules_status = WINNING_YES; + + if(checkrules_status == WINNING_YES) + { + //print("WINNING\n"); + NextLevel(); + } +} + +string GotoMap(string m) +{ + m = GameTypeVote_MapInfo_FixName(m); + if (!m) + return "The map you suggested is not available on this server."; + if (!autocvar_sv_vote_gametype) + if(!MapInfo_CheckMap(m)) + return "The map you suggested does not support the current game mode."; + cvar_set("nextmap", m); + cvar_set("timelimit", "-1"); + if(mapvote_initialized || alreadychangedlevel) + { + if(DoNextMapOverride(0)) + return "Map switch initiated."; + else + return "Hm... no. For some reason I like THIS map more."; + } + else + return "Map switch will happen after scoreboard."; +} + +bool autocvar_sv_gameplayfix_multiplethinksperframe = true; +void RunThink(entity this) +{ + // don't let things stay in the past. + // it is possible to start that way by a trigger with a local time. + if(this.nextthink <= 0 || this.nextthink > time + frametime) + return; + + float oldtime = time; // do we need to save this? + + for (int iterations = 0; iterations < 128 && !wasfreed(this); iterations++) + { + time = max(oldtime, this.nextthink); + this.nextthink = 0; + + if(getthink(this)) + getthink(this)(this); + // mods often set nextthink to time to cause a think every frame, + // we don't want to loop in that case, so exit if the new nextthink is + // <= the time the qc was told, also exit if it is past the end of the + // frame + if(this.nextthink <= time || this.nextthink > oldtime + frametime || !autocvar_sv_gameplayfix_multiplethinksperframe) + break; + } + + time = oldtime; +} + +bool autocvar_sv_freezenonclients; +bool autocvar_sv_gameplayfix_delayprojectiles = false; +void Physics_Frame() +{ + if(autocvar_sv_freezenonclients) + return; + + IL_EACH(g_moveables, true, + { + if(IS_CLIENT(it) || it.classname == "" || it.move_movetype == MOVETYPE_PHYSICS) + continue; + + //set_movetype(it, it.move_movetype); + // inline the set_movetype function, since this is called a lot + it.movetype = (it.move_qcphysics) ? MOVETYPE_QCENTITY : it.move_movetype; + + if(it.move_qcphysics && it.move_movetype != MOVETYPE_NONE) + Movetype_Physics_NoMatchTicrate(it, PHYS_INPUT_TIMELENGTH, false); + + if(it.movetype >= MOVETYPE_USER_FIRST && it.movetype <= MOVETYPE_USER_LAST) // these cases have no think handling + { + if(it.move_movetype == MOVETYPE_PUSH || it.move_movetype == MOVETYPE_FAKEPUSH) + continue; // these movetypes have no regular think function + // handle thinking here + if (getthink(it) && it.nextthink > 0 && it.nextthink <= time + frametime) + RunThink(it); + } + }); + + if(autocvar_sv_gameplayfix_delayprojectiles >= 0) + return; + + IL_EACH(g_moveables, it.move_qcphysics, + { + if(IS_CLIENT(it) || it.classname == "" || it.move_movetype == MOVETYPE_NONE) + continue; + Movetype_Physics_NoMatchTicrate(it, PHYS_INPUT_TIMELENGTH, false); + }); +} + +void systems_update(); +void EndFrame() +{ + anticheat_endframe(); + + Physics_Frame(); + + FOREACH_CLIENT(IS_REAL_CLIENT(it), { + entity e = IS_SPEC(it) ? it.enemy : it; + if (e.typehitsound) { + STAT(TYPEHIT_TIME, it) = time; + } else if (e.killsound) { + STAT(KILL_TIME, it) = time; + } else if (e.damage_dealt) { + STAT(HIT_TIME, it) = time; + STAT(DAMAGE_DEALT_TOTAL, it) += ceil(e.damage_dealt); + } + }); + // add 1 frametime because after this, engine SV_Physics + // increases time by a frametime and then networks the frame + // add another frametime because client shows everything with + // 1 frame of lag (cl_nolerp 0). The last +1 however should not be + // needed! + float altime = time + frametime * (1 + autocvar_g_antilag_nudge); + FOREACH_CLIENT(true, { + it.typehitsound = false; + it.damage_dealt = 0; + it.killsound = false; + antilag_record(it, CS(it), altime); + }); + IL_EACH(g_monsters, true, + { + antilag_record(it, it, altime); + }); + IL_EACH(g_projectiles, it.classname == "nade", + { + antilag_record(it, it, altime); + }); + systems_update(); + IL_ENDFRAME(); +} + + +/* + * RedirectionThink: + * returns true if redirecting + */ +float redirection_timeout; +float redirection_nextthink; +float RedirectionThink() +{ + float clients_found; + + if(redirection_target == "") + return false; + + if(!redirection_timeout) + { + cvar_set("sv_public", "-2"); + redirection_timeout = time + 0.6; // this will only try twice... should be able to keep more clients + if(redirection_target == "self") + bprint("^3SERVER NOTICE:^7 restarting the server\n"); + else + bprint("^3SERVER NOTICE:^7 redirecting everyone to ", redirection_target, "\n"); + } + + if(time < redirection_nextthink) + return true; + + redirection_nextthink = time + 1; + + clients_found = 0; + FOREACH_CLIENT(IS_REAL_CLIENT(it), { + // TODO add timer + LOG_INFO("Redirecting: sending connect command to ", it.netname); + if(redirection_target == "self") + stuffcmd(it, "\ndisconnect; defer ", ftos(autocvar_quit_and_redirect_timer), " reconnect\n"); + else + stuffcmd(it, strcat("\ndisconnect; defer ", ftos(autocvar_quit_and_redirect_timer), " \"connect ", redirection_target, "\"\n")); + ++clients_found; + }); + + LOG_INFO("Redirecting: ", ftos(clients_found), " clients left."); + + if(time > redirection_timeout || clients_found == 0) + localcmd("\nwait; wait; wait; quit\n"); + + return true; +} + +void RestoreGame() +{ + // Loaded from a save game + // some things then break, so let's work around them... + + // Progs DB (capture records) + ServerProgsDB = db_load(strcat("server.db", autocvar_sessionid)); + + // Mapinfo + MapInfo_Shutdown(); + MapInfo_Enumerate(); + MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags(), 1); + WeaponStats_Init(); + + TargetMusic_RestoreGame(); +} + +void Shutdown() +{ + game_stopped = 2; + + if(world_initialized > 0) + { + world_initialized = 0; + + // if a timeout is active, reset the slowmo value to normal + if(timeout_status == TIMEOUT_ACTIVE) + cvar_set("slowmo", ftos(orig_slowmo)); + + LOG_TRACE("Saving persistent data..."); + Ban_SaveBans(); + + // playerstats with unfinished match + PlayerStats_GameReport(false); + + if(!cheatcount_total) + { + if(autocvar_sv_db_saveasdump) + db_dump(ServerProgsDB, strcat("server.db", autocvar_sessionid)); + else + db_save(ServerProgsDB, strcat("server.db", autocvar_sessionid)); + } + if(autocvar_developer > 0) + { + if(autocvar_sv_db_saveasdump) + db_dump(TemporaryDB, "server-temp.db"); + else + db_save(TemporaryDB, "server-temp.db"); + } + CheatShutdown(); // must be after cheatcount check + db_close(ServerProgsDB); + db_close(TemporaryDB); + LOG_TRACE("Saving persistent data... done!"); + // tell the bot system the game is ending now + bot_endgame(); + + WeaponStats_Shutdown(); + MapInfo_Shutdown(); + } + else if(world_initialized == 0) + { + LOG_INFO("NOTE: crashed before even initializing the world, not saving persistent data"); + } + else + { + __init_dedicated_server_shutdown(); + } +} diff --git a/qcsrc/server/world.qh b/qcsrc/server/world.qh new file mode 100644 index 000000000..3bbaad682 --- /dev/null +++ b/qcsrc/server/world.qh @@ -0,0 +1,65 @@ +#pragma once + +float checkrules_equality; +float checkrules_suddendeathwarning; +float checkrules_suddendeathend; +float checkrules_overtimesadded; //how many overtimes have been already added + +// flag set on worldspawn so that the code knows if it is dedicated or not +bool server_is_dedicated; + +string cvar_changes; +string cvar_purechanges; +float cvar_purechanges_count; + +string modname; + +string gamemode_name; + +string clientstuff; + +string matchid; + +.string fog; + +float intermission_running; +float intermission_exittime; +float alreadychangedlevel; + +string cache_mutatormsg; +string cache_lastmutatormsg; + +float default_player_alpha; +float default_weapon_alpha; + +// database +float ServerProgsDB; +float TemporaryDB; + +.float winning; +const int WINNING_NO = 0; // no winner, but time limits may terminate the game +const int WINNING_YES = 1; // winner found +const int WINNING_NEVER = 2; // no winner, enter overtime if time limit is reached +const int WINNING_STARTSUDDENDEATHOVERTIME = 3; // no winner, enter suddendeath overtime NOW + +float WinningCondition_Scores(float limit, float leadlimit); +void SetWinners(.float field, float value); +void IntermissionThink(entity this); +void GotoNextMap(float reinit); +void ReadyRestart(); + +string GetGametype(); + +void DumpStats(float final); +float Map_IsRecent(string m); +string GetNextMap(); +void ShuffleMaplist(); +void Map_Goto_SetStr(string nextmapname); +void Map_Goto(float reinit); +void Map_MarkAsRecent(string m); +float DoNextMapOverride(float reinit); +void CheckRules_World(); +float RedirectionThink(); + +IntrusiveList g_moveables; +STATIC_INIT(g_moveables) { g_moveables = IL_NEW(); }