]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/gamemodes/gamemode/tmayhem/sv_tmayhem.qc
Merge branch 'master' into z411/bai-server
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / gamemodes / gamemode / tmayhem / sv_tmayhem.qc
1 #include "sv_tmayhem.qh"
2
3 float autocvar_g_tmayhem_fraglimit;
4 float autocvar_g_tmayhem_visual_score_limit;
5 float autocvar_g_tmayhem_score_leadlimit;
6 bool autocvar_g_tmayhem_team_spawns;
7
8 // TODO? rename to teammayhem? requires checking alias and other string lengths
9 int autocvar_g_tmayhem_teams;
10 int autocvar_g_tmayhem_teams_override;
11
12 bool autocvar_g_tmayhem_regenerate;
13 bool autocvar_g_tmayhem_rot;
14 string autocvar_g_tmayhem_weaponarena;
15 bool autocvar_g_tmayhem_powerups;
16 bool autocvar_g_tmayhem_selfdamage;
17 int autocvar_g_tmayhem_scoringmethod;
18 float autocvar_g_tmayhem_scoringmethod_1_damage_weight;
19 float autocvar_g_tmayhem_scoringmethod_1_frag_weight;
20 bool autocvar_g_tmayhem_scoringmethod_1_disable_selfdamage2score;
21 bool autocvar_g_tmayhem_pickup_items;
22 bool autocvar_g_tmayhem_pickup_items_remove_weapons_and_ammo;
23 bool autocvar_g_tmayhem_unlimited_ammo;
24
25 float autocvar_g_tmayhem_start_health = 200;
26 float autocvar_g_tmayhem_start_armor = 200;
27 float autocvar_g_tmayhem_start_ammo_shells = 60;
28 float autocvar_g_tmayhem_start_ammo_nails = 320;
29 float autocvar_g_tmayhem_start_ammo_rockets = 160;
30 float autocvar_g_tmayhem_start_ammo_cells = 180;
31 float autocvar_g_tmayhem_start_ammo_plasma = 180;
32 float autocvar_g_tmayhem_start_ammo_fuel = 0;
33
34 .float total_damage_dealt;
35
36 // code from here on is just to support maps that don't have team entities
37 void tmayhem_SpawnTeam (string teamname, int teamcolor)
38 {
39         entity this = new_pure(tmayhem_team);
40         this.netname = teamname;
41         this.cnt = teamcolor - 1;
42         this.team = teamcolor;
43         this.spawnfunc_checked = true;
44         //spawnfunc_tmayhem_team(this);
45 }
46
47 void tmayhem_DelayedInit(entity this)
48 {
49         // if no teams are found, spawn defaults
50         if (find(NULL, classname, "tmayhem_team") == NULL)
51         {
52                 LOG_TRACE("No \"tmayhem_team\" entities found on this map, creating them anyway.");
53
54                 int numteams = autocvar_g_tmayhem_teams_override;
55                 if(numteams < 2) { numteams = autocvar_g_tmayhem_teams; }
56
57                 int teams = BITS(bound(2, numteams, 4));
58                 if(teams & BIT(0))
59                         tmayhem_SpawnTeam("Red", NUM_TEAM_1);
60                 if(teams & BIT(1))
61                         tmayhem_SpawnTeam("Blue", NUM_TEAM_2);
62                 if(teams & BIT(2))
63                         tmayhem_SpawnTeam("Yellow", NUM_TEAM_3);
64                 if(teams & BIT(3))
65                         tmayhem_SpawnTeam("Pink", NUM_TEAM_4);
66         }
67 }
68
69 void tmayhem_Initialize()
70 {
71         GameRules_teams(true);
72         GameRules_spawning_teams(autocvar_g_tmayhem_team_spawns);
73         GameRules_limit_lead(autocvar_g_tmayhem_score_leadlimit);
74
75         if (autocvar_fraglimit_override != 0)
76                 GameRules_limit_score(autocvar_g_tmayhem_visual_score_limit);
77         else
78                 GameRules_limit_score(9999);
79
80         InitializeEntity(NULL, tmayhem_DelayedInit, INITPRIO_GAMETYPE);
81 }
82 // code up to here is just to support maps that don't have team entities
83
84 MUTATOR_HOOKFUNCTION(tmayhem, TeamBalance_CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
85 {
86         M_ARGV(1, string) = "tmayhem_team";
87 }
88
89 MUTATOR_HOOKFUNCTION(tmayhem, Scores_CountFragsRemaining)
90 {
91         // do not announce remaining frags, upscaled score count doesn't match well with this
92         // when scorelimit is set to 1000 it would announce 997, 998 and 999 score counts
93         // usually a single shot which deals ~40-80 dmg gives 2 or 3 score
94         // this usually would cause a "2 fra..." announcement to be played as the match ends 
95         // without leaving anyone time to even process the announcement
96         return false;
97 }
98
99 MUTATOR_HOOKFUNCTION(tmayhem, SetStartItems)
100 {
101         start_items       &= ~(IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS);
102         if (!cvar("g_use_ammunition") || autocvar_g_tmayhem_unlimited_ammo)
103                 start_items |= IT_UNLIMITED_AMMO;
104
105         start_health       = warmup_start_health       = autocvar_g_tmayhem_start_health;
106         start_armorvalue   = warmup_start_armorvalue   = autocvar_g_tmayhem_start_armor;
107         start_ammo_shells  = warmup_start_ammo_shells  = autocvar_g_tmayhem_start_ammo_shells;
108         start_ammo_nails   = warmup_start_ammo_nails   = autocvar_g_tmayhem_start_ammo_nails;
109         start_ammo_rockets = warmup_start_ammo_rockets = autocvar_g_tmayhem_start_ammo_rockets;
110         start_ammo_cells   = warmup_start_ammo_cells   = autocvar_g_tmayhem_start_ammo_cells;
111         start_ammo_plasma  = warmup_start_ammo_plasma  = autocvar_g_tmayhem_start_ammo_plasma;
112         start_ammo_fuel    = warmup_start_ammo_fuel    = autocvar_g_tmayhem_start_ammo_fuel;
113 }
114
115 MUTATOR_HOOKFUNCTION(tmayhem, PlayerRegen)
116 {
117         if(!autocvar_g_tmayhem_regenerate)
118                 M_ARGV(2, float) = 0;
119         if(!autocvar_g_tmayhem_rot)
120                 M_ARGV(3, float) = 0;
121         return (!autocvar_g_tmayhem_regenerate && !autocvar_g_tmayhem_rot);
122 }
123
124 MUTATOR_HOOKFUNCTION(tmayhem, ForbidThrowCurrentWeapon)
125 {
126         return true;
127 }
128
129 MUTATOR_HOOKFUNCTION(tmayhem, SetWeaponArena)
130 {
131         if (M_ARGV(0, string) == "0" || M_ARGV(0, string) == "")
132                 M_ARGV(0, string) = autocvar_g_tmayhem_weaponarena;
133 }
134
135 MUTATOR_HOOKFUNCTION(tmayhem, FilterItem)
136 {
137         entity item = M_ARGV(0, entity);
138         
139         //enable powerups if forced globally or global accepts gamemodes to have powerups according to their own settings
140         if (autocvar_g_powerups == 1 || (autocvar_g_powerups == -1 && autocvar_g_tmayhem_powerups == 1)){
141                 if (item.itemdef.instanceOfPowerup){
142                         return false;
143                 } 
144         }
145         //disabled powerups if forced off globally or in this gamemode
146         if (autocvar_g_powerups == 0 || autocvar_g_tmayhem_powerups == 0){
147                 if (item.itemdef.instanceOfPowerup){
148                         return true; 
149                 } 
150         }
151         //remove all items if items are forced off globally
152         if (autocvar_g_pickup_items == 0){
153                 return true;
154         }
155         //if items are switched on in this gamemode allow the removal of weapons and ammo still
156         if ((autocvar_g_tmayhem_pickup_items == 1 && autocvar_g_tmayhem_pickup_items_remove_weapons_and_ammo == 1) && autocvar_g_pickup_items <= 0){
157                 if (item.itemdef.instanceOfAmmo || item.itemdef.instanceOfWeaponPickup){
158                         return true;
159                 }
160         }
161         //remove items if not globally set to follow mode's settings and locally set off
162         if (autocvar_g_pickup_items == -1 && autocvar_g_tmayhem_pickup_items == 0){
163                 return true;
164         }
165         return false;
166 }
167
168 MUTATOR_HOOKFUNCTION(tmayhem, Damage_Calculate)
169 {
170         entity frag_attacker = M_ARGV(1, entity);
171         entity frag_target = M_ARGV(2, entity);
172         float frag_deathtype = M_ARGV(3, float);
173         float frag_damage = M_ARGV(4, float);
174         float frag_mirrordamage = M_ARGV(5, float);
175
176         if (IS_PLAYER(frag_target)) //don't ever zero damage to non-players
177         if (!IS_DEAD(frag_target)) //enable anyone to gib corpses
178         if ((autocvar_g_tmayhem_selfdamage == 0 && frag_target == frag_attacker) || frag_deathtype == DEATH_FALL.m_id) //nullify self-damage if self-damage is disabled and always nullify splat
179                 frag_damage = 0;
180
181         frag_mirrordamage = 0; //no mirror damaging
182
183         M_ARGV(4, float) = frag_damage;
184         M_ARGV(5, float) = frag_mirrordamage;
185 }
186
187 void TeamMayhemCalculatePlayerScore(entity scorer)
188 {
189         if (autocvar_fraglimit_override > 0) autocvar_g_tmayhem_fraglimit = autocvar_fraglimit_override;
190         
191         switch (autocvar_g_tmayhem_scoringmethod)
192         {
193                 default:
194                 case 1:
195                 {
196                         //calculate how much score the player should have based on their damage dealt and frags gotten and then add the missing score
197
198                         //give a different weight for suicides if scoring method 1 doesn't have selfdamage2score enabled to harshly punish for suicides to avoid exploiting
199                         float suicide_weight = 1 + (autocvar_g_tmayhem_scoringmethod_1_disable_selfdamage2score * (1/autocvar_g_tmayhem_scoringmethod_1_frag_weight));
200
201                         //total damage divided by player start health&armor to get how many lives worth of damage they've dealt, then how much that is out of the fraglimit, then calculate new value affected by weight
202                         float playerdamagescore = (((((scorer.total_damage_dealt/(start_health + start_armorvalue)) * 100)/autocvar_g_tmayhem_fraglimit) * autocvar_g_tmayhem_scoringmethod_1_damage_weight) );
203                                                                                                                                                                                                         //  * 100 to avoid float inaccuracy at that decimal level
204
205                         //playerdamagescore rounded
206                         float roundedplayerdamagescore = ((rint(playerdamagescore*10))/10);
207
208                         //kills minus suicides, total out of fraglimit, calculate weight
209                         float playerkillscore = ((((PlayerScore_Get(scorer, SP_KILLS) - (PlayerScore_Get(scorer, SP_SUICIDES) * suicide_weight)) * 100) / autocvar_g_tmayhem_fraglimit) * autocvar_g_tmayhem_scoringmethod_1_frag_weight);
210                                                                                                                                                                                                                                                                 //   * 100 to avoid float inaccuracy at that decimal level
211
212                         //only used for debug print, add killscore and damagescore together
213                         float playerscore = (roundedplayerdamagescore + playerkillscore);
214
215                         //add killscore and damagescore together to get total score and then adjust it to be total out of the visual score limit
216                         float playerscorevisual = ((roundedplayerdamagescore + playerkillscore) * autocvar_g_tmayhem_visual_score_limit);
217                                                                                                                                                                                                                                                 
218
219                         //calculated how much score the player has and now calculate total of how much they are supposed to have
220                         float scoretoadd = (playerscorevisual - (PlayerScore_Get(scorer, SP_SCORE) * 100));
221                                                                                                                                                                    //  * 100 to avoid float inaccuracy at that decimal level
222
223                         //adjust total score to be what the player is supposed to have
224                         GameRules_scoring_add_team(scorer, SCORE, floor(scoretoadd / 100));
225                                                                                                                                         // / 100 to move back to the decimal level
226
227                         if(0){
228                                 //debug printing
229                                 if(!IS_BOT_CLIENT(scorer)){
230                                         print(sprintf("%f", scorer.total_damage_dealt),                 " scorer.total_damage_dealt \n");
231                                         print(sprintf("%f", playerdamagescore),                                 " playerdamagescore \n");
232                                         print(sprintf("%f", roundedplayerdamagescore),                  " rounded playerdamagescore \n");
233                                         print(sprintf("%f", playerkillscore),                                   " playerkillscore \n");
234                                         print(sprintf("%f", PlayerScore_Get(scorer, SP_KILLS)), " PlayerScore_Get(scorer, SP_KILLS) \n");
235                                         print(sprintf("%f", playerscore),                                               " playerscore \n");
236                                         print(sprintf("%f", playerscorevisual),                                 " visual playerscore \n");
237                                         print(sprintf("%f", scoretoadd),                                                " scoretoadd \n");
238                                         print(sprintf("%f", PlayerScore_Get(scorer, SP_SCORE)), " PlayerScore_Get(scorer, SP_SCORE) \n \n");
239                                 }
240                         }
241                 return;
242                 }
243
244                 case 2:
245                 {
246                         //calculate how much score the player should have based on their frags gotten and then add the missing score
247                         float playerkillscore = (((PlayerScore_Get(scorer, SP_KILLS) - PlayerScore_Get(scorer, SP_SUICIDES)) * 100)/ autocvar_g_tmayhem_fraglimit);
248                         float playerscorevisual = (playerkillscore * autocvar_g_tmayhem_visual_score_limit) / 100;
249                         float scoretoadd = (playerscorevisual - PlayerScore_Get(scorer, SP_SCORE));
250                         GameRules_scoring_add_team(scorer, SCORE, floor(scoretoadd));
251
252                         if(0){
253                                 //debug printing
254                                 if(!IS_BOT_CLIENT(scorer)){
255                                         print(sprintf("%f", playerkillscore),                                   " playerkillscore \n");
256                                         print(sprintf("%f", PlayerScore_Get(scorer, SP_KILLS)), " PlayerScore_Get(scorer, SP_KILLS) \n");
257                                         print(sprintf("%f", playerscorevisual),                                 " visual playerscore \n");
258                                         print(sprintf("%f", scoretoadd),                                                " scoretoadd \n");
259                                         print(sprintf("%f", PlayerScore_Get(scorer, SP_SCORE)), " PlayerScore_Get(scorer, SP_SCORE) \n \n");
260                                 }
261                         }
262                 return;
263                 }
264
265                 case 3:
266                 {
267                         //calculate how much score the player should have based on their damage dealt and then add the missing score
268                         float playerdamagescore = (((scorer.total_damage_dealt/(start_health + start_armorvalue)) * 100)/autocvar_g_tmayhem_fraglimit);
269                         float roundedplayerdamagescore = ((rint(playerdamagescore*10))/10);
270                         float playerscorevisual = (roundedplayerdamagescore * autocvar_g_tmayhem_visual_score_limit);
271                         float scoretoadd = (playerscorevisual - (PlayerScore_Get(scorer, SP_SCORE) * 100));
272                         GameRules_scoring_add_team(scorer, SCORE, floor(scoretoadd / 100));
273
274                         if(0){
275                                 //debug printing
276                                 if(!IS_BOT_CLIENT(scorer)){
277                                         print(sprintf("%f", scorer.total_damage_dealt),                 " scorer.total_damage_dealt \n");
278                                         print(sprintf("%f", playerdamagescore),                                 " playerdamagescore \n");
279                                         print(sprintf("%f", roundedplayerdamagescore),                  " rounded playerdamagescore \n");
280                                         print(sprintf("%f", playerscorevisual),                                 " visual playerscore \n");
281                                         print(sprintf("%f", scoretoadd),                                                " scoretoadd \n");
282                                         print(sprintf("%f", PlayerScore_Get(scorer, SP_SCORE)), " PlayerScore_Get(scorer, SP_SCORE) \n \n");
283                                 }
284                         }
285                 return;
286                 }
287         }
288 }
289
290 MUTATOR_HOOKFUNCTION(tmayhem, PlayerDamage_SplitHealthArmor)
291 {
292         if (autocvar_g_tmayhem_scoringmethod==2) return;
293         
294         entity frag_target = M_ARGV(2, entity);
295
296         if (StatusEffects_active(STATUSEFFECT_SpawnShield, frag_target) && autocvar_g_spawnshield_blockdamage >= 1)return;
297         
298         entity frag_attacker = M_ARGV(1, entity);
299         float frag_deathtype = M_ARGV(6, float);
300         float frag_damage = M_ARGV(7, float);
301         float damage_take = bound(0, M_ARGV(4, float), GetResource(frag_target, RES_HEALTH));
302         float damage_save = bound(0, M_ARGV(5, float), GetResource(frag_target, RES_ARMOR));
303         float excess = max(0, frag_damage - damage_take - damage_save);
304         float total = frag_damage - excess;
305
306         if (total == 0) return;
307
308         if (StatusEffects_active(STATUSEFFECT_SpawnShield, frag_target) && autocvar_g_spawnshield_blockdamage < 1)
309                 total *= 1 - bound(0, autocvar_g_spawnshield_blockdamage, 1);
310
311         entity scorer = NULL; //entity which needs their score to be updated
312
313         if (IS_PLAYER(frag_attacker))
314         {
315                 //non-friendly fire
316                 if (!SAME_TEAM(frag_target, frag_attacker))
317                         frag_attacker.total_damage_dealt += total;
318
319                 //friendly fire aka self damage
320                 if (SAME_TEAM(frag_target, frag_attacker) || (frag_target == frag_attacker && !autocvar_g_tmayhem_scoringmethod_1_disable_selfdamage2score))
321                         frag_attacker.total_damage_dealt -= total;
322
323                 scorer = frag_attacker;
324         }
325         else
326         {
327                 //handle (environmental hazard) suiciding, check first if player has a registered attacker who most likely pushed them there to avoid punishing pushed players as pushers are already rewarded
328                 //deathtypes:
329                 //kill = suicide, drown = drown in water/liquid, hurttrigger = out of the map void or hurt triggers inside maps like electric sparks
330                 //camp = campcheck, lava = lava, slime = slime
331                 //team change / rebalance suicides are currently not included
332                 if (!autocvar_g_tmayhem_scoringmethod_1_disable_selfdamage2score && (
333                         frag_deathtype == DEATH_KILL.m_id ||
334                         frag_deathtype == DEATH_DROWN.m_id ||
335                         frag_deathtype == DEATH_HURTTRIGGER.m_id ||
336                         frag_deathtype == DEATH_CAMP.m_id ||
337                         frag_deathtype == DEATH_LAVA.m_id ||
338                         frag_deathtype == DEATH_SLIME.m_id ||
339                         frag_deathtype == DEATH_SWAMP.m_id))
340                                 frag_target.total_damage_dealt -= total;
341
342                 scorer = frag_target;
343         }
344
345         TeamMayhemCalculatePlayerScore(scorer);
346 }
347
348 MUTATOR_HOOKFUNCTION(tmayhem, GiveFragsForKill, CBC_ORDER_FIRST)
349 {
350         entity frag_attacker = M_ARGV(0, entity);
351         M_ARGV(2, float) = 0; //score to give for the frag directly
352         
353         if (IS_PLAYER(frag_attacker)) TeamMayhemCalculatePlayerScore(frag_attacker);
354         
355         return true;
356 }