set g_cts_respawn_delay 0.25
set g_cts_selfdamage 1 "0 = disable all selfdamage and falldamage in cts"
set g_cts_finish_kill_delay 10 "prevent cheating by running back to the start line, and starting out with more speed than otherwise possible"
+set g_ka_respawn_delay 0
+set g_ka_respawn_waves 0
// overtime
seta timelimit_overtime 2 "duration in minutes of one added overtime, added to the timelimit"
seta g_keyhunt_teams_override 0
set g_keyhunt_teams 0
+// keepaway
+set g_keepaway 0 "game mode which focuses around a ball, look at g_keepaway_win_mode for further details"
+set g_keepaway_bckillscore 1 "enable scoring points (y/n) for ball carrier kills"
+set g_keepaway_pointlimit -1 "total amount of points you can get, -1 for unlimited"
+set g_keepaway_pointleadlimit -1 "mercy rule, -1 for unlimited"
+set g_keepaway_ballcarrier_alpha 0.6 "alpha when the player is the ballcarrier"
+set g_keepaway_ballcarrier_highspeed 1.5 "speed multiplier done to the person holding the ball"
+set g_keepaway_ballcarrier_damage 1.5 "damage multiplier while having powerup"
+set g_keepaway_ballcarrier_force 1.5 "force multiplier while having powerup"
+set g_keepaway_ballcarrier_selfdamage 1 "self damage multiplier while having powerup"
+set g_keepaway_ballcarrier_selfforce 1.5 "self force multiplier while having powerup"
+set g_keepaway_noncarrier_warn 0 "warn players when they kill without holding the ball"
+set g_keepaway_noncarrier_damage 0.5 "damage done to other players if both you and they don't have the ball"
+set g_keepaway_noncarrier_force 0.5 "force done to other players if both you and they don't have the ball"
+set g_keepaway_noncarrier_selfdamage 1 "self damage if you don't have the ball"
+set g_keepaway_noncarrier_selfforce 1 "self force if you don't have the ball"
+set g_keepawayball_trail_color 254 "particle trail color from player/ball"
+set g_keepawayball_damageforcescale 2 "Scale of force which is applied to the ball by weapons/explosions/etc"
+set g_keepawayball_respawntime 15 "if no one picks up the ball, how long to wait until the ball respawns"
+seta g_keepaway_teams_override 0
+set g_keepaway_teams 0
+
// so it can be stuffcmd-ed still
set cl_gravity 800 "but ignored anyway"
alias cl_hook_gamestart_rc
alias cl_hook_gamestart_nexball
alias cl_hook_gamestart_cts
+alias cl_hook_gamestart_ka
alias cl_hook_gameend
alias cl_hook_activeweapon
alias sv_hook_gamestart_rc
alias sv_hook_gamestart_nexball
alias sv_hook_gamestart_cts
+alias sv_hook_gamestart_ka
alias sv_hook_gamerestart
alias sv_hook_gameend
alpha 256 256 1024
velocityjitter 256 256 256
+//sparks for keepaway ball touch
+// used nowhere in code
+effect kaball_sparks
+count 35
+type spark
+tex 40 40
+color 0xa9cacf 0x0054ff
+size 1 3
+alpha 0 256 556
+gravity 1
+bounce 1.5
+originjitter 1 1 1
+velocityjitter 300 300 300
+velocitymultiplier 0.5
+airfriction 3
sprite race-finish "Finish" ff8000 000000 0.0
sprite race-start "Start" ff8000 000000 0.0
sprite nb-ball "Ball" e8d8a0 000000 0.0
+sprite ka-ball "Ball" 00ffff 000000 0.0
+sprite ka-ballcarrier "Ball carrier" ff0000 000000 0.0
sprite wpn-laser "Laser" ff8080 000000 0.0 # bright red
sprite wpn-shotgun "Shotgun" 804000 000000 0.0 # brown
else if(type == RACE_FAIL) {
HUD_KillNotify_Push(s1, s2, 1, RACE_FAIL);
}
+ } else if(msg == MSG_KA) {
+ if(type == KA_PICKUPBALL) {
+ HUD_KillNotify_Push(s1, s2, 0, KA_PICKUPBALL);
+ if(alsoprint)
+ print (s1, "^7 has picked up the ball!\n");
+ }
+ else if(type == KA_DROPBALL) {
+ HUD_KillNotify_Push(s1, s2, 0, KA_DROPBALL);
+ if(alsoprint)
+ print(s1, "^7 has dropped the ball!\n");
+ }
}
}
s = "notify_blue_captured";
}
}
+ else if(killnotify_deathtype[j] == KA_DROPBALL)
+ {
+ s = "notify_balldropped";
+ }
+ else if(killnotify_deathtype[j] == KA_PICKUPBALL)
+ {
+ s = "notify_ballpickedup";
+ }
+
attacker = textShortenToWidth(killnotify_attackers[j], 0.48 * mySize_x - height, fontsize, stringwidth_colors);
pos_attacker = pos + eX * (0.52 * mySize_x + height) + eY * (0.5 * fontsize_y + i * height);
weap_pos = pos + eX * 0.5 * mySize_x - eX * height + eY * i * height;
}
}
+// Keepaway HUD mod icon
+float kaball_prevstatus; // last remembered status
+float kaball_statuschange_time; // time when the status changed
+
+// we don't need to reset for keepaway since it immediately
+// autocorrects prevstatus as to if the player has the ball or not
+
+void HUD_Mod_Keepaway(vector pos, vector mySize)
+{
+ mod_active = 1; // keepaway should always show the mod HUD
+
+ float BLINK_FACTOR = 0.15;
+ float BLINK_BASE = 0.85;
+ float BLINK_FREQ = 5;
+ float kaball_alpha = BLINK_BASE + BLINK_FACTOR * cos(time * BLINK_FREQ);
+
+ float stat_items = getstati(STAT_ITEMS);
+ float kaball = (stat_items/IT_KEY1) & 1;
+
+ if(kaball != kaball_prevstatus)
+ {
+ kaball_statuschange_time = time;
+ kaball_prevstatus = kaball;
+ }
+
+ // todo: Fix the sizing with the expanding image
+
+ float kaball_statuschange_elapsedtime = time - kaball_statuschange_time;
+ float f = bound(0, kaball_statuschange_elapsedtime*2, 1);
+
+ if(kaball_prevstatus && f < 1)
+ drawpic_aspect_skin_expanding(pos + eY * 0.25 * mySize_y, "keepawayball_carrying", eX * mySize_x + eY * mySize_y * 0.5, '1 1 1', panel_fg_alpha * kaball_alpha, DRAWFLAG_NORMAL, f);
+
+ if(kaball)
+ drawpic_aspect_skin(pos, "keepawayball_carrying", eX * mySize_x + eY * mySize_y, '1 1 1', panel_fg_alpha * kaball_alpha * f, DRAWFLAG_NORMAL);
+}
+
+
// Nexball HUD mod icon
void HUD_Mod_NexBall(vector pos, vector mySize)
{
if(!autocvar_hud_panel_modicons && !autocvar__hud_configure)
return;
- if (gametype != GAME_KEYHUNT && gametype != GAME_CTF && gametype != GAME_NEXBALL && gametype != GAME_CTS && gametype != GAME_RACE && gametype != GAME_CA && !autocvar__hud_configure)
+ if (gametype != GAME_KEYHUNT && gametype != GAME_CTF && gametype != GAME_NEXBALL && gametype != GAME_CTS && gametype != GAME_RACE && gametype != GAME_CA && gametype != GAME_KEEPAWAY && !autocvar__hud_configure)
return;
active_panel = HUD_PANEL_MODICONS;
HUD_Mod_Race(pos, mySize);
else if(gametype == GAME_CA)
HUD_Mod_CA(pos, mySize);
+ else if(gametype == GAME_KEEPAWAY)
+ HUD_Mod_Keepaway(pos, mySize);
}
// Draw pressed keys (#11)
{
return strcat( // fteqcc sucks
"ping pl name | ",
- "-teams,race,lms/kills -teams,lms/deaths -teams,lms,race/suicides -race,dm,tdm/frags ", // tdm already has this in "score"
+ "-teams,race,lms/kills -teams,lms/deaths -teams,lms,race,ka/suicides -race,dm,tdm,ka/frags ", // tdm already has this in "score"
"+ctf/caps +ctf/pickups +ctf/fckills +ctf/returns ",
"+lms/lives +lms/rank ",
"+kh/caps +kh/pushes +kh/destroyed ",
"?+race/laps ?+race/time ?+race/fastest ",
- "+as/objectives +nexball/faults +nexball/goals ",
+ "+as/objectives +nexball/faults +nexball/goals +ka/drops +ka/pickups +ka/bckills ",
"-lms,race,nexball/score");
}
const float GAME_ARENA = 7;
const float GAME_KEYHUNT = 8;
const float GAME_ASSAULT = 9;
-const float GAME_ONSLAUGHT = 10;
-const float GAME_RACE = 11;
-const float GAME_NEXBALL = 12;
-const float GAME_CTS = 13;
-const float GAME_CA = 14;
+const float GAME_ONSLAUGHT = 10;
+const float GAME_RACE = 11;
+const float GAME_NEXBALL = 12;
+const float GAME_CTS = 13;
+const float GAME_CA = 14;
+const float GAME_KEEPAWAY = 15;
const float AS_STRING = 1;
const float AS_INT = 2;
float MSG_KILL_ACTION = 3;
float MSG_KILL_ACTION_SPREE = 4;
float MSG_INFO = 5;
-
+float MSG_KA = 6;
float MSG_RACE = 10;
float KILL_TEAM_RED = 10301;
float INFO_RETURNFLAG = 10322;
float INFO_CAPTUREFLAG = 10323;
+float KA_PICKUPBALL = 10350;
+float KA_DROPBALL = 10351;
+
float RACE_SERVER_RECORD = 10400;
float RACE_NEW_TIME = 10401;
float RACE_NEW_RANK = 10402;
MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_DEATHMATCH; // DM always works
MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_RUNEMATCH; // Rune always works
MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_LMS; // LMS always works
+ MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_KEEPAWAY; // Keepaway always works
if(spawnpoints >= 8 && diameter > 4096) {
MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_TEAM_DEATHMATCH;
case MAPINFO_TYPE_ONSLAUGHT: return "20 0";
case MAPINFO_TYPE_NEXBALL: return "5 20 0";
case MAPINFO_TYPE_CTS: return "20 0 0";
+ case MAPINFO_TYPE_KEEPAWAY: return "30 20 0";
default: return "";
}
}
s = cdr(s);
}
+ if(pWantedType == MAPINFO_TYPE_KEEPAWAY)
+ {
+ sa = car(s);
+ if(sa != "")
+ cvar_set("fraglimit", sa);
+ s = cdr(s);
+ }
+
// rc = timelimit timelimit_qualification laps laps_teamplay
if(pWantedType == MAPINFO_TYPE_RACE)
{
case MAPINFO_TYPE_ONSLAUGHT: return "timelimit=20";
case MAPINFO_TYPE_NEXBALL: return "timelimit=20 pointlimit=5 leadlimit=0";
case MAPINFO_TYPE_CTS: return "timelimit=20 skill=-1";
+ case MAPINFO_TYPE_KEEPAWAY: return "timelimit=20 pointlimit=30";
default: return "";
}
}
else if(t == "rc") return MAPINFO_TYPE_RACE;
else if(t == "nexball") return MAPINFO_TYPE_NEXBALL;
else if(t == "cts") return MAPINFO_TYPE_CTS;
+ else if(t == "keepaway") return MAPINFO_TYPE_KEEPAWAY;
else if(t == "all") return MAPINFO_TYPE_ALL;
else return 0;
}
else if(t == MAPINFO_TYPE_RACE) return "rc";
else if(t == MAPINFO_TYPE_NEXBALL) return "nexball";
else if(t == MAPINFO_TYPE_CTS) return "cts";
+ else if(t == MAPINFO_TYPE_KEEPAWAY) return "keepaway";
else if(t == MAPINFO_TYPE_ALL) return "all";
else return "";
}
return MAPINFO_TYPE_NEXBALL;
else if(cvar("g_cts"))
return MAPINFO_TYPE_CTS;
+ else if(cvar("g_keepaway"))
+ return MAPINFO_TYPE_KEEPAWAY;
else
return MAPINFO_TYPE_DEATHMATCH;
}
{
switch(t)
{
- case MAPINFO_TYPE_DEATHMATCH: return "g_dm";
- case MAPINFO_TYPE_TEAM_DEATHMATCH: return "g_tdm";
- case MAPINFO_TYPE_DOMINATION: return "g_domination";
- case MAPINFO_TYPE_CTF: return "g_ctf";
- case MAPINFO_TYPE_RUNEMATCH: return "g_runematch";
- case MAPINFO_TYPE_LMS: return "g_lms";
- case MAPINFO_TYPE_ARENA: return "g_arena";
- case MAPINFO_TYPE_CA: return "g_ca";
- case MAPINFO_TYPE_KEYHUNT: return "g_kh";
- case MAPINFO_TYPE_ASSAULT: return "g_assault";
- case MAPINFO_TYPE_ONSLAUGHT: return "g_onslaught";
- case MAPINFO_TYPE_RACE: return "g_race";
- case MAPINFO_TYPE_NEXBALL: return "g_nexball";
- case MAPINFO_TYPE_CTS: return "g_cts";
+ case MAPINFO_TYPE_DEATHMATCH: return "g_dm";
+ case MAPINFO_TYPE_TEAM_DEATHMATCH: return "g_tdm";
+ case MAPINFO_TYPE_DOMINATION: return "g_domination";
+ case MAPINFO_TYPE_CTF: return "g_ctf";
+ case MAPINFO_TYPE_RUNEMATCH: return "g_runematch";
+ case MAPINFO_TYPE_LMS: return "g_lms";
+ case MAPINFO_TYPE_ARENA: return "g_arena";
+ case MAPINFO_TYPE_CA: return "g_ca";
+ case MAPINFO_TYPE_KEYHUNT: return "g_kh";
+ case MAPINFO_TYPE_ASSAULT: return "g_assault";
+ case MAPINFO_TYPE_ONSLAUGHT: return "g_onslaught";
+ case MAPINFO_TYPE_RACE: return "g_race";
+ case MAPINFO_TYPE_NEXBALL: return "g_nexball";
+ case MAPINFO_TYPE_CTS: return "g_cts";
+ case MAPINFO_TYPE_KEEPAWAY: return "g_keepaway";
default: return "";
}
}
cvar_set("g_race", (t == MAPINFO_TYPE_RACE) ? "1" : "0");
cvar_set("g_nexball", (t == MAPINFO_TYPE_NEXBALL) ? "1" : "0");
cvar_set("g_cts", (t == MAPINFO_TYPE_CTS) ? "1" : "0");
+ cvar_set("g_keepaway", (t == MAPINFO_TYPE_KEEPAWAY) ? "1" : "0");
}
void MapInfo_LoadMap(string s)
float MAPINFO_TYPE_ASSAULT = 2048;
float MAPINFO_TYPE_ONSLAUGHT = 4096;
float MAPINFO_TYPE_NEXBALL = 8192;
-float MAPINFO_TYPE_ALL = 16383; // this has to include all above bits
+float MAPINFO_TYPE_KEEPAWAY = 16384;
+float MAPINFO_TYPE_ALL = 32767; // this has to include all above bits
float MAPINFO_FEATURE_WEAPONS = 1; // not defined for minstagib-only maps
else if (g == GAME_RACE) return "rc";
else if (g == GAME_NEXBALL) return "nexball";
else if (g == GAME_CTS) return "cts";
+ else if (g == GAME_KEEPAWAY) return "ka";
return "dm";
}
if(e.checked) e0 = NULL;
me.TD(me, 1, me.columns / n, e = makeXonoticGametypeButton(1, "g_arena", "Arena"));
if(e.checked) e0 = NULL;
+ me.TD(me, 1, me.columns / n, e = makeXonoticGametypeButton(1, "g_keepaway", "Keepaway"));
+ if(e.checked) e0 = NULL;
me.TD(me, 1, me.columns / n, e = makeXonoticGametypeButton(1, "g_race", "Race"));
if(e.checked) e0 = NULL;
me.TD(me, 1, me.columns / n, e = makeXonoticGametypeButton(1, "g_cts", "Race CTS"));
ATTRIB(XonoticMapInfoDialog, typeRaceLabel, entity, NULL)
ATTRIB(XonoticMapInfoDialog, typeCTSLabel, entity, NULL)
ATTRIB(XonoticMapInfoDialog, typeNexballLabel, entity, NULL)
+ ATTRIB(XonoticMapInfoDialog, typeKeepawayLabel, entity, NULL)
ATTRIB(XonoticMapInfoDialog, currentMapIndex, float, 0)
ATTRIB(XonoticMapInfoDialog, currentMapBSPName, string, string_null)
me.typeRaceLabel.disabled = !(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_RACE);
me.typeCTSLabel.disabled = !(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_CTS);
me.typeNexballLabel.disabled = !(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_NEXBALL);
+ me.typeKeepawayLabel.disabled = !(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_KEEPAWAY);
MapInfo_ClearTemps();
}
me.typeCTSLabel = e;
me.TD(me, 1, wgt, e = makeXonoticTextLabel(0, "Nexball"));
me.typeNexballLabel = e;
+ me.TD(me, 1, wgt, e = makeXonoticTextLabel(0, "Keepaway"));
+ me.typeKeepawayLabel = e;
me.gotoRC(me, me.rows - 2, 0);
me.TD(me, 1, me.columns, e = makeXonoticTextLabel(0.5, ""));
}
};
+// Keepaway
+// If you don't have the ball, get it; if you do, kill people.
+void havocbot_role_ka()
+{
+ if(self.deadflag != DEAD_NO)
+ return;
+
+ if (self.bot_strategytime < time)
+ {
+ self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
+ navigation_goalrating_start();
+ havocbot_goalrating_items(10000, self.origin, 10000);
+ havocbot_goalrating_enemyplayers(20000, self.origin, 10000);
+ //havocbot_goalrating_waypoints(1, self.origin, 1000);
+ navigation_goalrating_end();
+ }
+}
+
void havocbot_chooserole_dm()
{
self.havocbot_role = havocbot_role_dm;
self.havocbot_role = havocbot_role_dom;
};
+void havocbot_chooserole_ka()
+{
+ self.havocbot_role = havocbot_role_ka;
+}
+
void havocbot_chooserole()
{
dprint("choosing a role...\n");
havocbot_chooserole_race();
else if (g_onslaught)
havocbot_chooserole_ons();
+ else if (g_keepaway)
+ havocbot_chooserole_ka();
else // assume anything else is deathmatch
havocbot_chooserole_dm();
};
if(self.flagcarried)
DropFlag(self.flagcarried, world, world);
- if(self.ballcarried)
+ if(self.ballcarried && g_nexball)
DropBall(self.ballcarried, self.origin + self.ballcarried.origin, self.velocity);
WaypointSprite_PlayerDead();
if(self.flagcarried)
DropFlag(self.flagcarried, world, world);
- if(self.ballcarried)
+ if(self.ballcarried && g_nexball)
DropBall(self.ballcarried, self.origin + self.ballcarried.origin, self.velocity);
// Here, everything has been done that requires this player to be a client.
void player_powerups (void)
{
+ // add a way to see what the items were BEFORE all of these checks for the mutator hook
+ olditems = self.items;
+
if((self.items & IT_USING_JETPACK) && !self.deadflag)
{
SoundEntity_StartSound(self, CHAN_PLAYER, "misc/jetpack_fly.wav", VOL_BASE, cvar("g_jetpack_attenuation"));
sprint(self, "^3You are on speed\n");
}
}
- return;
}
-
- if (self.items & IT_STRENGTH)
+ else // if we're not in minstagib, continue. I added this else to replace the "return" which was here that broke the callhook for this function -- This code is nasty.
{
- play_countdown(self.strength_finished, "misc/poweroff.wav");
- self.effects = self.effects | (EF_BLUE | EF_ADDITIVE | EF_FULLBRIGHT);
- if (time > self.strength_finished && cvar("g_balance_powerup_timer"))
+ if (self.items & IT_STRENGTH)
{
- self.items = self.items - (self.items & IT_STRENGTH);
- sprint(self, "^3Strength has worn off\n");
+ play_countdown(self.strength_finished, "misc/poweroff.wav");
+ self.effects = self.effects | (EF_BLUE | EF_ADDITIVE | EF_FULLBRIGHT);
+ if (time > self.strength_finished && cvar("g_balance_powerup_timer"))
+ {
+ self.items = self.items - (self.items & IT_STRENGTH);
+ sprint(self, "^3Strength has worn off\n");
+ }
}
- }
- else
- {
- if (time < self.strength_finished)
+ else
{
- self.items = self.items | IT_STRENGTH;
- sprint(self, "^3Strength infuses your weapons with devastating power\n");
+ if (time < self.strength_finished)
+ {
+ self.items = self.items | IT_STRENGTH;
+ sprint(self, "^3Strength infuses your weapons with devastating power\n");
+ }
}
- }
- if (self.items & IT_INVINCIBLE)
- {
- play_countdown(self.invincible_finished, "misc/poweroff.wav");
- self.effects = self.effects | (EF_RED | EF_ADDITIVE | EF_FULLBRIGHT);
- if (time > self.invincible_finished && cvar("g_balance_powerup_timer"))
+ if (self.items & IT_INVINCIBLE)
{
- self.items = self.items - (self.items & IT_INVINCIBLE);
- sprint(self, "^3Shield has worn off\n");
+ play_countdown(self.invincible_finished, "misc/poweroff.wav");
+ self.effects = self.effects | (EF_RED | EF_ADDITIVE | EF_FULLBRIGHT);
+ if (time > self.invincible_finished && cvar("g_balance_powerup_timer"))
+ {
+ self.items = self.items - (self.items & IT_INVINCIBLE);
+ sprint(self, "^3Shield has worn off\n");
+ }
}
- }
- else
- {
- if (time < self.invincible_finished)
+ else
{
- self.items = self.items | IT_INVINCIBLE;
- sprint(self, "^3Shield surrounds you\n");
+ if (time < self.invincible_finished)
+ {
+ self.items = self.items | IT_INVINCIBLE;
+ sprint(self, "^3Shield surrounds you\n");
+ }
}
- }
- if(cvar("g_nodepthtestplayers"))
- self.effects = self.effects | EF_NODEPTHTEST;
+ if(cvar("g_nodepthtestplayers"))
+ self.effects = self.effects | EF_NODEPTHTEST;
- if(cvar("g_fullbrightplayers"))
- self.effects = self.effects | EF_FULLBRIGHT;
+ if(cvar("g_fullbrightplayers"))
+ self.effects = self.effects | EF_FULLBRIGHT;
- // midair gamemode: damage only while in the air
- // if in midair mode, being on ground grants temporary invulnerability
- // (this is so that multishot weapon don't clear the ground flag on the
- // first damage in the frame, leaving the player vulnerable to the
- // remaining hits in the same frame)
- if (self.flags & FL_ONGROUND)
- if (g_midair)
- self.spawnshieldtime = max(self.spawnshieldtime, time + cvar("g_midair_shieldtime"));
+ // midair gamemode: damage only while in the air
+ // if in midair mode, being on ground grants temporary invulnerability
+ // (this is so that multishot weapon don't clear the ground flag on the
+ // first damage in the frame, leaving the player vulnerable to the
+ // remaining hits in the same frame)
+ if (self.flags & FL_ONGROUND)
+ if (g_midair)
+ self.spawnshieldtime = max(self.spawnshieldtime, time + cvar("g_midair_shieldtime"));
- if (time >= game_starttime)
- if (time < self.spawnshieldtime)
- self.effects = self.effects | (EF_ADDITIVE | EF_FULLBRIGHT);
+ if (time >= game_starttime)
+ if (time < self.spawnshieldtime)
+ self.effects = self.effects | (EF_ADDITIVE | EF_FULLBRIGHT);
+ }
+
+ MUTATOR_CALLHOOK(PlayerPowerups);
}
float CalcRegen(float current, float stable, float regenfactor, float regenframetime)
maxspd_mod = 1;
if(g_minstagib && (self.items & IT_INVINCIBLE))
maxspd_mod *= cvar("g_minstagib_speed_highspeed");
- if(g_nexball && self.ballcarried)
- maxspd_mod *= cvar("g_nexball_basketball_carrier_highspeed");
+ if(self.ballcarried)
+ if(g_nexball)
+ maxspd_mod *= cvar("g_nexball_basketball_carrier_highspeed");
+ else if(g_keepaway)
+ maxspd_mod *= cvar("g_keepaway_ballcarrier_highspeed");
+
if(g_runematch)
{
if(self.runes & RUNE_SPEED)
damage_take = take;
damage_save = save;
damage_force = force;
- MUTATOR_CALLHOOK(PlayerDamage);
+ MUTATOR_CALLHOOK(PlayerDamage_SplitHealthArmor);
take = bound(0, damage_take, self.health);
save = bound(0, damage_save, self.armorvalue);
else
DropFlag(self.flagcarried, world, attacker);
}
- if(self.ballcarried)
+ if(self.ballcarried && g_nexball)
DropBall(self.ballcarried, self.origin, self.velocity);
Portal_ClearAllLater(self);
// clear waypoints
if(self.classname == "player" && cvar("sv_spectate") == 1) {
if(self.flagcarried)
DropFlag(self.flagcarried, world, world);
- if(self.ballcarried)
+ if(self.ballcarried && g_nexball)
DropBall(self.ballcarried, self.origin, self.velocity);
WaypointSprite_PlayerDead();
self.classname = "observer";
float ctf_score_value(string parameter);
-float g_dm, g_domination, g_ctf, g_tdm, g_keyhunt, g_onslaught, g_assault, g_arena, g_ca, g_lms, g_runematch, g_race, g_nexball, g_cts;
+float g_dm, g_domination, g_ctf, g_tdm, g_keyhunt, g_onslaught, g_assault, g_arena, g_ca, g_lms, g_runematch, g_race, g_nexball, g_cts, g_keepaway;
float g_cloaked, g_footsteps, g_jump_grunt, g_grappling_hook, g_midair, g_minstagib, g_pinata, g_norecoil, g_minstagib_invis_alpha, g_bloodloss;
float g_warmup_limit;
float g_warmup_allguns;
.string target4;
.float trigger_reverse;
-// Nexball
-.entity ballcarried;
+// Nexball
+.entity ballcarried; // Also used for keepaway
.float metertime;
float g_nexball_meter_period;
force = force * g_weaponforcefactor;
mirrorforce *= g_weaponforcefactor;
}
-
+
+ // should this be changed at all? If so, in what way?
+ frag_attacker = attacker;
+ frag_target = targ;
+ frag_damage = damage;
+ frag_force = force;
+ MUTATOR_CALLHOOK(PlayerDamage_Calculate);
+ damage = frag_damage;
+ force = frag_force;
+
// apply strength multiplier
if ((attacker.items & IT_STRENGTH) && !g_minstagib)
{
BADCVAR("g_runematch");
BADCVAR("g_tdm");
BADCVAR("g_nexball");
+ BADCVAR("g_keepaway");
BADCVAR("teamplay");
// long
entity self;
entity other;
-MUTATOR_HOOKABLE(PlayerDamage);
+MUTATOR_HOOKABLE(PlayerDamage_SplitHealthArmor);
// called when a player gets damaged to e.g. remove stuff he was carrying.
// INPUT:
entity frag_inflictor;
entity frag_attacker;
entity frag_target; // same as self
- vector damage_force; // NOTE: this force already HAS been applied (create and use a Damage hook to change that one)
+ vector damage_force; // NOTE: this force already HAS been applied
// INPUT, OUTPUT:
float damage_take;
float damage_save;
+
+MUTATOR_HOOKABLE(PlayerDamage_Calculate);
+ // called to adjust damage and force values which are applied to the player, used for e.g. strength damage/force multiplier or runematch runes
+ // i'm not sure if I should change this around slightly (Naming of the entities, and also how they're done in g_damage).
+ // INPUT:
+ entity frag_attacker;
+ entity frag_target;
+ // INPUT, OUTPUT:
+ float frag_damage;
+ vector frag_force;
+
+MUTATOR_HOOKABLE(PlayerPowerups);
+ // called at the end of player_powerups() in cl_client.qc, used for manipulating the values which are set by powerup items.
+ // INPUT
+ entity self;
+ float olditems; // also technically output, but since it is at the end of the function it's useless for that :P
\ No newline at end of file
--- /dev/null
+void ka_SpawnBall(void);
+void ka_TouchEvent(void);
+void ka_RespawnBall(void);
+void ka_DropEvent(entity);
+
+float ka_ballcarrier_waypointsprite_visible_for_player(entity);
+
+void ka_Initialize() // run at the start of a match, initiates game mode
+{
+ if(!g_keepaway)
+ return;
+
+ precache_sound("keepaway/pickedup.wav");
+ precache_sound("keepaway/dropped.wav");
+ precache_sound("keepaway/respawn.wav");
+ precache_sound("keepaway/touch.wav");
+
+ ScoreRules_keepaway();
+ ka_SpawnBall();
+}
+
+void ka_Reset() // used to clear the ballcarrier whenever the match switches from warmup to normal
+{
+ if(self.owner)
+ if(self.owner.classname == "player")
+ ka_DropEvent(self.owner);
+
+ ka_RespawnBall();
+}
+
+void ka_SpawnBall() // loads various values for the ball
+{
+ if(!g_keepaway) { return; }
+
+ entity e;
+ e = spawn();
+ e.model = "models/orbs/orbblue.md3";
+ e.scale = 1;
+ precache_model(e.model);
+ setmodel(e, e.model);
+ setsize(e, '-16 -16 -20', '16 16 20'); // 20 20 20 was too big, player is only 16 16 24... gotta cheat with the Z (20) axis so that the particle isn't cut off
+ e.classname = "keepawayball";
+ e.damageforcescale = cvar("g_keepawayball_damageforcescale");
+ e.takedamage = DAMAGE_YES;
+ e.glow_color = cvar("g_keepawayball_trail_color");
+ e.glow_trail = TRUE;
+ e.movetype = MOVETYPE_BOUNCE;
+ e.touch = ka_TouchEvent;
+ e.flags = FL_ITEM;
+ e.reset = ka_Reset;
+ e.owner = world;
+
+ InitializeEntity(e, ka_RespawnBall, INITPRIO_SETLOCATION); // is this the right priority? Neh, I have no idea.. Well-- it works! So.
+}
+
+void ka_RespawnBall() // runs whenever the ball needs to be relocated
+{
+ vector oldballorigin = self.origin;
+
+ if(MoveToRandomMapLocation(self, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, 10, 1024, 256))
+ {
+ makevectors(self.angles);
+ self.movetype = MOVETYPE_BOUNCE;
+ self.velocity = '0 0 200';
+ self.angles = '0 0 0';
+ self.solid = SOLID_TRIGGER;
+ self.think = ka_RespawnBall;
+ self.nextthink = time + cvar("g_keepawayball_respawntime");
+
+ pointparticles(particleeffectnum("electro_combo"), oldballorigin, '0 0 0', 1);
+ pointparticles(particleeffectnum("electro_combo"), self.origin, '0 0 0', 1);
+
+ WaypointSprite_Spawn("ka-ball", 0, 0, self, '0 0 64', world, self.team, self, waypointsprite_attachedforcarrier, FALSE);
+ WaypointSprite_UpdateTeamRadar(self.waypointsprite_attachedforcarrier, RADARICON_FLAGCARRIER, '0 1 1');
+ WaypointSprite_Ping(self.waypointsprite_attachedforcarrier);
+
+ sound(self, CHAN_AUTO, "keepaway/respawn.wav", VOL_BASE, ATTN_NONE); // ATTN_NONE (it's a sound intended to be heard anywhere)
+ }
+ else
+ {
+ ka_RespawnBall(); // finding a location failed, retry
+ }
+}
+
+void ka_TouchEvent() // runs any time that the ball comes in contact with something
+{
+ if(!self) { return; }
+ if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
+ { // The ball fell off the map, respawn it since players can't get to it
+ ka_RespawnBall();
+ return;
+ }
+ if(other.deadflag != DEAD_NO) { return; }
+ if(other.classname != "player")
+ { // The ball just touched an object, most likely the world
+ pointparticles(particleeffectnum("kaball_sparks"), self.origin, '0 0 0', 1);
+ sound(self, CHAN_AUTO, "keepaway/touch.wav", VOL_BASE, ATTN_NORM);
+ return;
+ }
+ else if(self.wait > time) { return; }
+
+ // attach the ball to the player
+ self.owner = other;
+ other.ballcarried = self;
+ setattachment(self, other, "");
+ setorigin(self, '3 0 20');
+
+ // make the ball invisible/unable to do anything
+ self.velocity = '0 0 0';
+ self.movetype = MOVETYPE_NONE;
+ self.touch = SUB_Null;
+ self.effects |= EF_NODRAW;
+ self.think = SUB_Null;
+ self.nextthink = 0;
+ self.takedamage = DAMAGE_NO;
+
+ // apply effects to player
+ other.glow_color = cvar("g_keepawayball_trail_color");
+ other.glow_trail = TRUE;
+ other.effects |= EF_DIMLIGHT;
+ other.alpha = cvar("g_keepaway_ballcarrier_alpha");
+ other.exteriorweaponentity.alpha = cvar("g_keepaway_ballcarrier_alpha");
+
+ // messages and sounds
+ Send_KillNotification(other.netname, "", "", KA_PICKUPBALL, MSG_KA);
+ WriteByte(MSG_BROADCAST, SVC_CENTERPRINT);
+ WriteString(MSG_BROADCAST, strcat("\n\n", other.netname, "^7 has picked up the ball!\n"));
+ sound(self.owner, CHAN_AUTO, "keepaway/pickedup.wav", VOL_BASE, ATTN_NONE); // ATTN_NONE (it's a sound intended to be heard anywhere)
+
+ // scoring
+ PlayerScore_Add(other, SP_KEEPAWAY_PICKUPS, 1);
+
+ // waypoints
+ WaypointSprite_AttachCarrier("ka-ballcarrier", other);
+ other.waypointsprite_attachedforcarrier.waypointsprite_visible_for_player = ka_ballcarrier_waypointsprite_visible_for_player;
+ WaypointSprite_UpdateRule(other.waypointsprite_attachedforcarrier, 0, SPRITERULE_DEFAULT);
+ WaypointSprite_UpdateTeamRadar(other.waypointsprite_attachedforcarrier, RADARICON_FLAGCARRIER, '1 0 0');
+ WaypointSprite_Ping(other.waypointsprite_attachedforcarrier);
+ WaypointSprite_Kill(self.waypointsprite_attachedforcarrier);
+}
+
+void ka_DropEvent(entity plyr) // runs any time that a player is supposed to lose the ball
+{
+ entity ball;
+ ball = plyr.ballcarried;
+
+ if(!ball) { return; }
+
+ // reset the ball
+ setattachment(ball, world, "");
+ ball.movetype = MOVETYPE_BOUNCE;
+ ball.solid = SOLID_TRIGGER; // is this needed?
+ ball.wait = time + 1;
+ ball.think = ka_RespawnBall;
+ ball.nextthink = time + cvar("g_keepawayball_respawntime");
+ ball.touch = ka_TouchEvent;
+ ball.takedamage = DAMAGE_YES;
+ ball.effects &~= EF_NODRAW;
+ setorigin(ball, plyr.origin + '0 0 10');
+ ball.velocity = '0 0 200' + '0 100 0'*crandom() + '100 0 0'*crandom();
+ ball.owner.ballcarried = world; // I hope nothing checks to see if the world has the ball in the rest of my code :P
+ ball.owner = world;
+
+ // reset the player effects
+ plyr.effects &~= EF_DIMLIGHT;
+ plyr.alpha = default_player_alpha;
+ plyr.exteriorweaponentity.alpha = default_weapon_alpha;
+ plyr.glow_trail = FALSE;
+
+ // messages and sounds
+ Send_KillNotification(plyr.netname, "", "", KA_DROPBALL, MSG_KA);
+ WriteByte(MSG_BROADCAST, SVC_CENTERPRINT);
+ WriteString(MSG_BROADCAST, strcat("\n\n", plyr.netname, "^7 has dropped the ball!\n"));
+ sound(other, CHAN_AUTO, "keepaway/dropped.wav", VOL_BASE, ATTN_NONE); // ATTN_NONE (it's a sound intended to be heard anywhere)
+
+ // scoring
+ PlayerScore_Add(plyr, SP_KEEPAWAY_DROPS, 1);
+
+ // waypoints
+ WaypointSprite_Spawn("ka-ball", 0, 0, ball, '0 0 64', world, ball.team, ball, waypointsprite_attachedforcarrier, FALSE);
+ WaypointSprite_UpdateRule(ball.waypointsprite_attachedforcarrier, 0, SPRITERULE_DEFAULT);
+ WaypointSprite_UpdateTeamRadar(ball.waypointsprite_attachedforcarrier, RADARICON_FLAGCARRIER, '0 1 1');
+ WaypointSprite_Ping(ball.waypointsprite_attachedforcarrier);
+ WaypointSprite_Kill(plyr.waypointsprite_attachedforcarrier);
+}
+
+float ka_ballcarrier_waypointsprite_visible_for_player(entity e) // runs on waypoints which are attached to ballcarriers, updates once per frame
+{
+ if(e.ballcarried)
+ {
+ if(other.classname == "spectator")
+ return FALSE; // we don't want spectators of the ballcarrier to see the attached waypoint on the top of their screen
+ else if(g_minstagib && (e.items & IT_STRENGTH))
+ return FALSE; // if the ballcarrier has invisibility, don't draw the waypoint as this is the function of invisibility in keepaway
+ }
+
+ return TRUE;
+}
+
+MUTATOR_HOOKFUNCTION(ka_RemovePlayer)
+{
+ if(self.ballcarried) { ka_DropEvent(self); } // a player with the ball has left the match, drop it
+ return 0;
+}
+
+MUTATOR_HOOKFUNCTION(ka_Scoring)
+{
+ if((frag_attacker != frag_target) && (frag_attacker.classname == "player"))
+ {
+ if(frag_target.ballcarried) { // add to amount of times killing carrier
+ PlayerScore_Add(frag_attacker, SP_KEEPAWAY_CARRIERKILLS, 1);
+ if(cvar("g_keepaway_bckillscore")) // add bckills to the score
+ PlayerScore_Add(frag_attacker, SP_KEEPAWAY_SCORE, 1);
+ }
+ else if(!frag_attacker.ballcarried)
+ if(cvar("g_keepaway_noncarrier_warn"))
+ centerprint_atprio(frag_attacker, (CENTERPRIO_SPAM + 5), "Killing people while you don't have the ball gives no points!");
+
+ if(frag_attacker.ballcarried) // add to amount of kills while ballcarrier
+ PlayerScore_Add(frag_attacker, SP_KEEPAWAY_SCORE, 1);
+ }
+
+ if(self.ballcarried) { ka_DropEvent(self); } // a player with the ball has died, drop it
+ return 0;
+}
+
+MUTATOR_HOOKFUNCTION(ka_GiveFragsForKill)
+{
+ frag_score = 0; // no frags counted in keepaway
+ return 0;
+}
+
+MUTATOR_HOOKFUNCTION(ka_PlayerPreThink)
+{
+ // clear the item used for the ball in keepaway
+ self.items &~= IT_KEY1;
+
+ // if the player has the ball, make sure they have the item for it (Used for HUD primarily)
+ if(self.ballcarried)
+ self.items |= IT_KEY1;
+
+ // drop the ball if the player presses the use button
+ if(self.BUTTON_USE)
+ if(self.ballcarried) { ka_DropEvent(self); }
+
+ return 0;
+}
+
+MUTATOR_HOOKFUNCTION(ka_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
+{
+ if(frag_attacker.ballcarried) // if the attacker is a ballcarrier
+ {
+ if(frag_target == frag_attacker) // damage done to yourself
+ {
+ frag_damage *= cvar("g_keepaway_ballcarrier_selfdamage");
+ frag_force *= cvar("g_keepaway_ballcarrier_selfforce");
+ }
+ else // damage done to noncarriers
+ {
+ frag_damage *= cvar("g_keepaway_ballcarrier_damage");
+ frag_force *= cvar("g_keepaway_ballcarrier_force");
+ }
+ }
+ else if not(frag_target.ballcarried) // if the target is a noncarrier
+ {
+ if(frag_target == frag_attacker) // damage done to yourself
+ {
+ frag_damage *= cvar("g_keepaway_noncarrier_selfdamage");
+ frag_force *= cvar("g_keepaway_noncarrier_selfforce");
+ }
+ else // damage done to other noncarriers
+ {
+ frag_damage *= cvar("g_keepaway_noncarrier_damage");
+ frag_force *= cvar("g_keepaway_noncarrier_force");
+ }
+ }
+ return 0;
+}
+
+MUTATOR_HOOKFUNCTION(ka_PlayerPowerups)
+{
+ if(self.ballcarried)
+ {
+ // if the player has the ball, force ballcarrier alpha upon them
+ self.alpha = cvar("g_keepaway_ballcarrier_alpha");
+ self.exteriorweaponentity.alpha = cvar("g_keepaway_ballcarrier_alpha");
+
+ // if we're in minstagib and a ballcarrier has just picked up invisibility,
+ // notify all the other players that the ballcarrier no longer has a waypoint
+ if(g_minstagib)
+ {
+ if(olditems & IT_STRENGTH)
+ {
+ if(time > self.strength_finished)
+ { // this only runs ONCE right after the player loses invisibility
+ bprint(self.netname, "^7 isn't invisible from radar anymore.\n");
+ }
+ }
+ else
+ {
+ if(time < self.strength_finished)
+ { // this only runs ONCE right after the player gains invisibility
+ bprint(self.netname, "^7 has picked up invisibility and can no longer be seen on radar!\n");
+ }
+ }
+ }
+ }
+ else if(g_minstagib)
+ {
+ // if we're in minstagib and a noncarrier has invisibility, assure that we apply the invisibility effects normally
+ if(olditems & IT_STRENGTH)
+ {
+ self.alpha = g_minstagib_invis_alpha;
+ self.exteriorweaponentity.alpha = g_minstagib_invis_alpha;
+ }
+ }
+ else
+ {
+ // if we're a normal player with no powerups that edit alpha make sure the alpha is default.
+ // (normal powerups just use EF_ADDITIVE)
+ self.alpha = default_player_alpha;
+ self.exteriorweaponentity.alpha = default_weapon_alpha;
+ }
+
+ return 0;
+}
+
+MUTATOR_DEFINITION(gamemode_keepaway)
+{
+ // I don't quite understand these orders, perhaps someone could enlighten me?
+ MUTATOR_HOOK(MakePlayerObserver, ka_RemovePlayer, CBC_ORDER_ANY);
+ MUTATOR_HOOK(ClientDisconnect, ka_RemovePlayer, CBC_ORDER_ANY);
+ MUTATOR_HOOK(PlayerDies, ka_Scoring, CBC_ORDER_ANY);
+ MUTATOR_HOOK(GiveFragsForKill, ka_GiveFragsForKill, CBC_ORDER_ANY);
+ MUTATOR_HOOK(PlayerPreThink, ka_PlayerPreThink, CBC_ORDER_FIRST);
+ MUTATOR_HOOK(PlayerDamage_Calculate, ka_PlayerDamage, CBC_ORDER_ANY);
+ MUTATOR_HOOK(PlayerPowerups, ka_PlayerPowerups, CBC_ORDER_ANY);
+
+ MUTATOR_ONADD
+ {
+ if(time > 1) // game loads at time 1
+ error("This is a game type and it cannot be added at runtime.");
+ g_keepaway = 1;
+ ka_Initialize();
+ }
+
+ MUTATOR_ONREMOVE
+ {
+ g_keepaway = 0;
+ error("This is a game type and it cannot be removed at runtime.");
+ }
+
+ return 0;
+}
\ No newline at end of file
MUTATOR_DEFINITION(mutator_vampire)
{
- MUTATOR_HOOK(PlayerDamage, vampire_PlayerDamage, CBC_ORDER_ANY);
+ MUTATOR_HOOK(PlayerDamage_SplitHealthArmor, vampire_PlayerDamage, CBC_ORDER_ANY);
MUTATOR_HOOK(BuildMutatorsString, vampire_BuildMutatorsString, CBC_ORDER_ANY);
MUTATOR_HOOK(BuildMutatorsPrettyString, vampire_BuildMutatorsPrettyString, CBC_ORDER_ANY);
MUTATOR_DECLARATION(gamemode_keyhunt);
+MUTATOR_DECLARATION(gamemode_keepaway);
MUTATOR_DECLARATION(mutator_nix);
MUTATOR_DECLARATION(mutator_dodging);
mutators/base.qc
mutators/gamemode_keyhunt.qc
+mutators/gamemode_keepaway.qc
mutators/mutator_nix.qc
mutators/mutator_dodging.qc
mutators/mutator_rocketflying.qc
ScoreInfo_SetLabel_PlayerScore(SP_NEXBALL_FAULTS, "faults", SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER);
ScoreRules_basics_end();
}
+
+// Keep Away stuff
+#define SP_KEEPAWAY_PICKUPS 4
+#define SP_KEEPAWAY_CARRIERKILLS 5
+#define SP_KEEPAWAY_DROPS 6
+#define SP_KEEPAWAY_SCORE 7
+void ScoreRules_keepaway()
+{
+ ScoreRules_basics(0, SFL_SORT_PRIO_PRIMARY, 0, FALSE); // SFL_SORT_PRIO_PRIMARY
+ ScoreInfo_SetLabel_PlayerScore(SP_KEEPAWAY_SCORE, "score", SFL_SORT_PRIO_PRIMARY);
+ ScoreInfo_SetLabel_PlayerScore(SP_KEEPAWAY_PICKUPS, "pickups", 0);
+ ScoreInfo_SetLabel_PlayerScore(SP_KEEPAWAY_CARRIERKILLS, "bckills", 0);
+ ScoreInfo_SetLabel_PlayerScore(SP_KEEPAWAY_DROPS, "drops", SFL_LOWER_IS_BETTER);
+ ScoreRules_basics_end();
+}
if (other.classname != "gib")
if (other.classname != "casing")
if (other.classname != "droppedweapon")
+ if (other.classname != "keepawayball")
if (!other.projectiledeathtype || other.classname == "bullet")
return;
cvar_set("g_race", ftos(g_race));
cvar_set("g_nexball", ftos(g_nexball));
cvar_set("g_cts", ftos(g_cts));
+ cvar_set("g_keepaway", ftos(g_keepaway));
}
void ReadGameCvars()
found += (g_race = (!found && (prev != GAME_RACE) && cvar("g_race")));
found += (g_nexball = (!found && (prev != GAME_NEXBALL) && cvar("g_nexball")));
found += (g_cts = (!found && (prev != GAME_CTS) && cvar("g_cts")));
+ found += (g_keepaway = (!found && (prev != GAME_KEEPAWAY) && cvar("g_keepaway")));
if(found)
break;
have_team_spawns = -1; // request team spawns
}
+ if(g_keepaway)
+ {
+ game = GAME_KEEPAWAY;
+ gamemode_name = "Keepaway";
+ MUTATOR_ADD(gamemode_keepaway);
+ }
+
if(teams_matter)
entcs_init();
--- /dev/null
+models/orbs/orbblue
+{
+ deformVertexes autosprite
+ dpnoshadow
+
+ {
+ map models/orbs/orbblue.tga
+ blendfunc add
+ tcmod page 4 4 0.05
+ rgbgen vertex
+ }
+}
+models/orbs/orbred
+{
+ deformVertexes autosprite
+ dpnoshadow
+
+ {
+ map models/orbs/orbred.tga
+ blendfunc add
+ tcmod page 4 4 0.05
+ rgbgen vertex
+ }
+}
+models/orbs/orbyellow
+{
+ deformVertexes autosprite
+ dpnoshadow
+
+ {
+ map models/orbs/orbyellow.tga
+ blendfunc add
+ tcmod page 4 4 0.05
+ rgbgen vertex
+ }
+}
+models/orbs/orbpink
+{
+ deformVertexes autosprite
+ dpnoshadow
+
+ {
+ map models/orbs/orbpink.tga
+ blendfunc add
+ tcmod page 4 4 0.05
+ rgbgen vertex
+ }
+}
\ No newline at end of file