1 #include "sv_tmayhem.qh"
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 float tmayhempointmultiplier;
9 // TODO? rename to teammayhem? requires checking alias and other string lengths
10 int autocvar_g_tmayhem_teams;
11 int autocvar_g_tmayhem_teams_override;
13 bool autocvar_g_tmayhem_regenerate;
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;
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;
34 .float total_damage_dealt;
36 // code from here on is just to support maps that don't have team entities
37 void tmayhem_SpawnTeam (string teamname, int teamcolor)
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);
47 void tmayhem_DelayedInit(entity this)
49 // if no teams are found, spawn defaults
50 if(find(NULL, classname, "tmayhem_team") == NULL)
52 LOG_TRACE("No \"tmayhem_team\" entities found on this map, creating them anyway.");
54 int numteams = autocvar_g_tmayhem_teams_override;
55 if(numteams < 2) { numteams = autocvar_g_tmayhem_teams; }
57 int teams = BITS(bound(2, numteams, 4));
59 tmayhem_SpawnTeam("Red", NUM_TEAM_1);
61 tmayhem_SpawnTeam("Blue", NUM_TEAM_2);
63 tmayhem_SpawnTeam("Yellow", NUM_TEAM_3);
65 tmayhem_SpawnTeam("Pink", NUM_TEAM_4);
69 void tmayhem_Initialize()
71 if(autocvar_g_tmayhem_visual_score_limit != 0 && autocvar_g_tmayhem_fraglimit != 0)
72 tmayhempointmultiplier = autocvar_g_tmayhem_visual_score_limit / autocvar_g_tmayhem_fraglimit;
74 GameRules_teams(true);
75 GameRules_spawning_teams(autocvar_g_tmayhem_team_spawns);
76 GameRules_limit_score(autocvar_g_tmayhem_visual_score_limit);
77 GameRules_limit_lead(autocvar_g_tmayhem_score_leadlimit);
79 InitializeEntity(NULL, tmayhem_DelayedInit, INITPRIO_GAMETYPE);
81 // code up to here is just to support maps that don't have team entities
83 MUTATOR_HOOKFUNCTION(tmayhem, TeamBalance_CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
85 M_ARGV(1, string) = "tmayhem_team";
88 MUTATOR_HOOKFUNCTION(tmayhem, Scores_CountFragsRemaining)
90 // announce remaining frags
94 MUTATOR_HOOKFUNCTION(tmayhem, SetStartItems)
96 start_items &= ~(IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS);
97 if(!cvar("g_use_ammunition") || autocvar_g_tmayhem_unlimited_ammo)
98 start_items |= IT_UNLIMITED_AMMO;
100 start_health = warmup_start_health = autocvar_g_tmayhem_start_health;
101 start_armorvalue = warmup_start_armorvalue = autocvar_g_tmayhem_start_armor;
102 start_ammo_shells = warmup_start_ammo_shells = autocvar_g_tmayhem_start_ammo_shells;
103 start_ammo_nails = warmup_start_ammo_nails = autocvar_g_tmayhem_start_ammo_nails;
104 start_ammo_rockets = warmup_start_ammo_rockets = autocvar_g_tmayhem_start_ammo_rockets;
105 start_ammo_cells = warmup_start_ammo_cells = autocvar_g_tmayhem_start_ammo_cells;
106 start_ammo_plasma = warmup_start_ammo_plasma = autocvar_g_tmayhem_start_ammo_plasma;
107 start_ammo_fuel = warmup_start_ammo_fuel = autocvar_g_tmayhem_start_ammo_fuel;
110 //this hook also enables rotting, as players spawn with more hp and armor than what default rot limits are set to this is a bad idea as of now until PlayerRegen is changed
111 MUTATOR_HOOKFUNCTION(tmayhem, PlayerRegen)
113 if(autocvar_g_tmayhem_regenerate)
118 MUTATOR_HOOKFUNCTION(tmayhem, ForbidThrowCurrentWeapon)
123 MUTATOR_HOOKFUNCTION(tmayhem, SetWeaponArena)
125 if (M_ARGV(0, string) == "0" || M_ARGV(0, string) == "")
126 M_ARGV(0, string) = autocvar_g_tmayhem_weaponarena;
129 MUTATOR_HOOKFUNCTION(tmayhem, FilterItem)
131 entity item = M_ARGV(0, entity);
133 //enable powerups if forced globally or global accepts gamemodes to have powerups according to their own settings
134 if (autocvar_g_powerups == 1 || (autocvar_g_powerups == -1 && autocvar_g_tmayhem_powerups == 1)){
135 if (item.itemdef.instanceOfPowerup){
139 //disabled powerups if forced off globally or in this gamemode
140 if (autocvar_g_powerups == 0 || autocvar_g_tmayhem_powerups == 0){
141 if (item.itemdef.instanceOfPowerup){
145 //remove all items if items are forced off globally
146 if (autocvar_g_pickup_items == 0){
149 //if items are switched on in this gamemode allow the removal of weapons and ammo still
150 if ((autocvar_g_tmayhem_pickup_items == 1 && autocvar_g_tmayhem_pickup_items_remove_weapons_and_ammo == 1) && autocvar_g_pickup_items <= 0){
151 if (item.itemdef.instanceOfAmmo || item.itemdef.instanceOfWeaponPickup){
155 //remove items if not globally set to follow mode's settings and locally set off
156 if (autocvar_g_pickup_items == -1 && autocvar_g_tmayhem_pickup_items == 0){
162 MUTATOR_HOOKFUNCTION(tmayhem, Damage_Calculate)
164 entity frag_attacker = M_ARGV(1, entity);
165 entity frag_target = M_ARGV(2, entity);
166 float frag_deathtype = M_ARGV(3, float);
167 float frag_damage = M_ARGV(4, float);
168 float frag_mirrordamage = M_ARGV(5, float);
170 if (IS_PLAYER(frag_target)) //don't ever zero damage to non-players
171 if (!IS_DEAD(frag_target)) //enable anyone to gib corpses
172 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
175 frag_mirrordamage = 0; //no mirror damaging
177 M_ARGV(4, float) = frag_damage;
178 M_ARGV(5, float) = frag_mirrordamage;
181 void TeamMayhemCalculatePlayerScore(entity scorer){
182 switch (autocvar_g_tmayhem_scoringmethod)
187 //calculate how much score the player should have based on their damage dealt and frags gotten and then add the missing score
189 //give a different weight for suicides if scoring method 1 doesn't have selfdamage2score enabled to harshly punish for suicides to avoid exploiting
190 float suicide_weight = 1 + (autocvar_g_tmayhem_scoringmethod_1_disable_selfdamage2score * (1/autocvar_g_tmayhem_scoringmethod_1_frag_weight));
192 //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
193 float playerdamagescore = (((((scorer.total_damage_dealt/(start_health + start_armorvalue)) * 100)/autocvar_g_tmayhem_fraglimit) * autocvar_g_tmayhem_scoringmethod_1_damage_weight) );
194 // * 100 to avoid float inaccuracy at that decimal level
196 //playerdamagescore rounded
197 float roundedplayerdamagescore = ((rint(playerdamagescore*10))/10);
199 //kills minus suicides, total out of fraglimit, calculate weight
200 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);
201 // * 100 to avoid float inaccuracy at that decimal level
203 //only used for debug print, add killscore and damagescore together
204 float playerscore = (roundedplayerdamagescore + playerkillscore);
206 //add killscore and damagescore together to get total score and then adjust it to be total out of the visual score limit
207 float playerscorevisual = ((roundedplayerdamagescore + playerkillscore) * autocvar_g_tmayhem_visual_score_limit);
210 //calculated how much score the player has and now calculate total of how much they are supposed to have
211 float scoretoadd = (playerscorevisual - (PlayerScore_Get(scorer, SP_SCORE) * 100));
212 // * 100 to avoid float inaccuracy at that decimal level
214 //adjust total score to be what the player is supposed to have
215 GameRules_scoring_add_team(scorer, SCORE, floor(scoretoadd / 100));
216 // / 100 to move back to the decimal level
220 if(!IS_BOT_CLIENT(scorer)){
221 print(sprintf("%f", scorer.total_damage_dealt), " scorer.total_damage_dealt \n");
222 print(sprintf("%f", playerdamagescore), " playerdamagescore \n");
223 print(sprintf("%f", roundedplayerdamagescore), " rounded playerdamagescore \n");
224 print(sprintf("%f", playerkillscore), " playerkillscore \n");
225 print(sprintf("%f", PlayerScore_Get(scorer, SP_KILLS)), " PlayerScore_Get(scorer, SP_KILLS) \n");
226 print(sprintf("%f", playerscore), " playerscore \n");
227 print(sprintf("%f", playerscorevisual), " visual playerscore \n");
228 print(sprintf("%f", scoretoadd), " scoretoadd \n");
229 print(sprintf("%f", PlayerScore_Get(scorer, SP_SCORE)), " PlayerScore_Get(scorer, SP_SCORE) \n \n");
237 //calculate how much score the player should have based on their frags gotten and then add the missing score
238 float playerkillscore = (((PlayerScore_Get(scorer, SP_KILLS) - PlayerScore_Get(scorer, SP_SUICIDES)) * 100)/ autocvar_g_tmayhem_fraglimit);
239 float playerscorevisual = (playerkillscore * autocvar_g_tmayhem_visual_score_limit) / 100;
240 float scoretoadd = (playerscorevisual - PlayerScore_Get(scorer, SP_SCORE));
241 GameRules_scoring_add_team(scorer, SCORE, floor(scoretoadd));
245 if(!IS_BOT_CLIENT(scorer)){
246 print(sprintf("%f", playerkillscore), " playerkillscore \n");
247 print(sprintf("%f", PlayerScore_Get(scorer, SP_KILLS)), " PlayerScore_Get(scorer, SP_KILLS) \n");
248 print(sprintf("%f", playerscorevisual), " visual playerscore \n");
249 print(sprintf("%f", scoretoadd), " scoretoadd \n");
250 print(sprintf("%f", PlayerScore_Get(scorer, SP_SCORE)), " PlayerScore_Get(scorer, SP_SCORE) \n \n");
258 //calculate how much score the player should have based on their damage dealt and then add the missing score
259 float playerdamagescore = (((scorer.total_damage_dealt/(start_health + start_armorvalue)) * 100)/autocvar_g_tmayhem_fraglimit);
260 float roundedplayerdamagescore = ((rint(playerdamagescore*10))/10);
261 float playerscorevisual = (roundedplayerdamagescore * autocvar_g_tmayhem_visual_score_limit);
262 float scoretoadd = (playerscorevisual - (PlayerScore_Get(scorer, SP_SCORE) * 100));
263 GameRules_scoring_add_team(scorer, SCORE, floor(scoretoadd / 100));
267 if(!IS_BOT_CLIENT(scorer)){
268 print(sprintf("%f", scorer.total_damage_dealt), " scorer.total_damage_dealt \n");
269 print(sprintf("%f", playerdamagescore), " playerdamagescore \n");
270 print(sprintf("%f", roundedplayerdamagescore), " rounded playerdamagescore \n");
271 print(sprintf("%f", playerscorevisual), " visual playerscore \n");
272 print(sprintf("%f", scoretoadd), " scoretoadd \n");
273 print(sprintf("%f", PlayerScore_Get(scorer, SP_SCORE)), " PlayerScore_Get(scorer, SP_SCORE) \n \n");
281 MUTATOR_HOOKFUNCTION(tmayhem, PlayerDamage_SplitHealthArmor)
283 if (autocvar_g_tmayhem_scoringmethod==2) return;
285 entity frag_target = M_ARGV(2, entity);
287 if (StatusEffects_active(STATUSEFFECT_SpawnShield, frag_target) && autocvar_g_spawnshield_blockdamage >= 1)return;
289 entity frag_attacker = M_ARGV(1, entity);
290 float frag_deathtype = M_ARGV(6, float);
291 float frag_damage = M_ARGV(7, float);
292 float damage_take = bound(0, M_ARGV(4, float), GetResource(frag_target, RES_HEALTH));
293 float damage_save = bound(0, M_ARGV(5, float), GetResource(frag_target, RES_ARMOR));
294 float excess = max(0, frag_damage - damage_take - damage_save);
295 float total = frag_damage - excess;
297 if (total == 0) return;
299 if (StatusEffects_active(STATUSEFFECT_SpawnShield, frag_target) && autocvar_g_spawnshield_blockdamage < 1)
300 total *= 1 - bound(0, autocvar_g_spawnshield_blockdamage, 1);
302 entity scorer = NULL; //entity which needs their score to be updated
304 if (IS_PLAYER(frag_attacker))
307 if (!SAME_TEAM(frag_target, frag_attacker))
308 frag_attacker.total_damage_dealt += total;
310 //friendly fire aka self damage
311 if (SAME_TEAM(frag_target, frag_attacker) || (frag_target == frag_attacker && !autocvar_g_tmayhem_scoringmethod_1_disable_selfdamage2score))
312 frag_attacker.total_damage_dealt -= total;
314 scorer = frag_attacker;
318 //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
320 //kill = suicide, drown = drown in water/liquid, hurttrigger = out of the map void or hurt triggers inside maps like electric sparks
321 //camp = campcheck, lava = lava, slime = slime
322 //team change / rebalance suicides are currently not included
323 if (!autocvar_g_tmayhem_scoringmethod_1_disable_selfdamage2score && (
324 frag_deathtype == DEATH_KILL.m_id ||
325 frag_deathtype == DEATH_DROWN.m_id ||
326 frag_deathtype == DEATH_HURTTRIGGER.m_id ||
327 frag_deathtype == DEATH_CAMP.m_id ||
328 frag_deathtype == DEATH_LAVA.m_id ||
329 frag_deathtype == DEATH_SLIME.m_id ||
330 frag_deathtype == DEATH_SWAMP.m_id))
331 frag_target.total_damage_dealt -= total;
333 scorer = frag_target;
336 TeamMayhemCalculatePlayerScore(scorer);
339 MUTATOR_HOOKFUNCTION(tmayhem, GiveFragsForKill, CBC_ORDER_FIRST)
341 entity frag_attacker = M_ARGV(0, entity);
342 M_ARGV(2, float) = 0; //score to give for the frag directly
344 if (IS_PLAYER(frag_attacker)) TeamMayhemCalculatePlayerScore(frag_attacker);