From: Mario <mario.mario@y7mail.com>
Date: Tue, 29 Jan 2013 12:31:43 +0000 (+1100)
Subject: Moving Domination into the mutator API
X-Git-Tag: xonotic-v0.7.0~103^2~2
X-Git-Url: https://git.rm.cloudns.org/?a=commitdiff_plain;h=78fd790dbb8690b148bd9270934b8e918a2fbc00;p=xonotic%2Fxonotic-data.pk3dir.git

Moving Domination into the mutator API
---

diff --git a/qcsrc/server/attic/domination.qc b/qcsrc/server/attic/domination.qc
new file mode 100644
index 000000000..6a0dcdf8b
--- /dev/null
+++ b/qcsrc/server/attic/domination.qc
@@ -0,0 +1,525 @@
+
+/*
+Domination as a plugin for netquake mods
+by LordHavoc (lordhavoc@ghdigital.com)
+
+How to add domination points to a mod:
+1. Add this line to progs.src above world.qc:
+domination.qc
+2. Comment out all lines in ClientObituary in client.qc that begin with targ.frags  or attacker.frags.
+3. Add this above spawnfunc_worldspawn in world.qc:
+void() dom_init;
+4. Add this line to the end of spawnfunc_worldspawn in world.qc:
+dom_init();
+
+Note: The only teams who can use dom control points are identified by spawnfunc_dom_team entities (if none exist these default to red and blue and use only quake models/sounds).
+*/
+
+#define DOMPOINTFRAGS frags
+
+.float enemy_playerid;
+.entity sprite;
+.float captime;
+
+// pps: points per second
+.float dom_total_pps;
+.float dom_pps_red;
+.float dom_pps_blue;
+.float dom_pps_yellow;
+.float dom_pps_pink;
+float total_pps;
+float pps_red;
+float pps_blue;
+float pps_yellow;
+float pps_pink;
+void set_dom_state(entity e)
+{
+	e.dom_total_pps = total_pps;
+	e.dom_pps_red = pps_red;
+	e.dom_pps_blue = pps_blue;
+	if(c3 >= 0)
+		e.dom_pps_yellow = pps_yellow;
+	if(c4 >= 0)
+		e.dom_pps_pink = pps_pink;
+}
+
+void() dom_controlpoint_setup;
+
+void LogDom(string mode, float team_before, entity actor)
+{
+	string s;
+	if(!autocvar_sv_eventlog)
+		return;
+	s = strcat(":dom:", mode);
+	s = strcat(s, ":", ftos(team_before));
+	s = strcat(s, ":", ftos(actor.playerid));
+	GameLogEcho(s);
+}
+
+void() dom_spawnteams;
+
+void dompoint_captured ()
+{
+	entity head;
+	float old_delay, old_team, real_team;
+
+	// now that the delay has expired, switch to the latest team to lay claim to this point
+	head = self.owner;
+
+	real_team = self.cnt;
+	self.cnt = -1;
+
+	LogDom("taken", self.team, self.dmg_inflictor);
+	self.dmg_inflictor = world;
+
+	self.goalentity = head;
+	self.model = head.mdl;
+	self.modelindex = head.dmg;
+	self.skin = head.skin;
+
+	//bprint(head.message);
+	//bprint("\n");
+
+	//bprint(^3head.netname);
+	//bprint(head.netname);
+	//bprint(self.message);
+	//bprint("\n");
+
+	float points, wait_time;
+	if (autocvar_g_domination_point_amt)
+		points = autocvar_g_domination_point_amt;
+	else
+		points = self.frags;
+	if (autocvar_g_domination_point_rate)
+		wait_time = autocvar_g_domination_point_rate;
+	else
+		wait_time = self.wait;
+
+	bprint("^3", head.netname, "^3", self.message);
+	if (points != 1)
+		bprint(" ^7(", ftos(points), " points every ", ftos(wait_time), " seconds)\n");
+	else
+		bprint(" ^7(", ftos(points), " point every ", ftos(wait_time), " seconds)\n");
+
+	if(self.enemy.playerid == self.enemy_playerid)
+		PlayerScore_Add(self.enemy, SP_DOM_TAKES, 1);
+	else
+		self.enemy = world;
+
+	if (head.noise != "")
+		if(self.enemy)
+			sound(self.enemy, CH_TRIGGER, head.noise, VOL_BASE, ATTN_NORM);
+		else
+			sound(self, CH_TRIGGER, head.noise, VOL_BASE, ATTN_NORM);
+	if (head.noise1 != "")
+		play2all(head.noise1);
+
+	//self.nextthink = time + autocvar_g_domination_point_rate;
+	//self.think = dompointthink;
+
+	self.delay = time + wait_time;
+
+	// do trigger work
+	old_delay = self.delay;
+	old_team = self.team;
+	self.team = real_team;
+	self.delay = 0;
+	activator = self;
+	SUB_UseTargets ();
+	self.delay = old_delay;
+	self.team = old_team;
+
+	switch(self.goalentity.team)
+	{
+		case COLOR_TEAM1:
+			WaypointSprite_UpdateSprites(self.sprite, "dom-red", "", "");
+			break;
+		case COLOR_TEAM2:
+			WaypointSprite_UpdateSprites(self.sprite, "dom-blue", "", "");
+			break;
+		case COLOR_TEAM3:
+			WaypointSprite_UpdateSprites(self.sprite, "dom-yellow", "", "");
+			break;
+		case COLOR_TEAM4:
+			WaypointSprite_UpdateSprites(self.sprite, "dom-pink", "", "");
+	}
+
+	total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
+	for(head = world; (head = find(head, classname, "dom_controlpoint")) != world; )
+	{
+		if (autocvar_g_domination_point_amt)
+			points = autocvar_g_domination_point_amt;
+		else
+			points = head.frags;
+		if (autocvar_g_domination_point_rate)
+			wait_time = autocvar_g_domination_point_rate;
+		else
+			wait_time = head.wait;
+		switch(head.goalentity.team)
+		{
+			case COLOR_TEAM1:
+				pps_red += points/wait_time;
+				break;
+			case COLOR_TEAM2:
+				pps_blue += points/wait_time;
+				break;
+			case COLOR_TEAM3:
+				pps_yellow += points/wait_time;
+				break;
+			case COLOR_TEAM4:
+				pps_pink += points/wait_time;
+		}
+		total_pps += points/wait_time;
+	}
+
+	WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, colormapPaletteColor(self.goalentity.team - 1, 0));
+	WaypointSprite_Ping(self.sprite);
+
+	self.captime = time;
+
+	FOR_EACH_REALCLIENT(head)
+		set_dom_state(head);
+}
+
+void AnimateDomPoint()
+{
+	if(self.pain_finished > time)
+		return;
+	self.pain_finished = time + self.t_width;
+	if(self.nextthink > self.pain_finished)
+		self.nextthink = self.pain_finished;
+
+	self.frame = self.frame + 1;
+	if(self.frame > self.t_length)
+		self.frame = 0;
+}
+
+void dompointthink()
+{
+	float fragamt;
+
+	self.nextthink = time + 0.1;
+
+	//self.frame = self.frame + 1;
+	//if(self.frame > 119)
+	//	self.frame = 0;
+	AnimateDomPoint();
+
+	// give points
+
+	if (gameover || self.delay > time || time < game_starttime)	// game has ended, don't keep giving points
+		return;
+
+	if(autocvar_g_domination_point_rate)
+		self.delay = time + autocvar_g_domination_point_rate;
+	else
+		self.delay = time + self.wait;
+
+	// give credit to the team
+	// NOTE: this defaults to 0
+	if (self.goalentity.netname != "")
+	{
+		if(autocvar_g_domination_point_amt)
+			fragamt = autocvar_g_domination_point_amt;
+		else
+			fragamt = self.DOMPOINTFRAGS;
+		TeamScore_AddToTeam(self.goalentity.team, ST_SCORE, fragamt);
+		TeamScore_AddToTeam(self.goalentity.team, ST_DOM_TICKS, fragamt);
+
+		// give credit to the individual player, if he is still there
+		if (self.enemy.playerid == self.enemy_playerid)
+		{
+			PlayerScore_Add(self.enemy, SP_SCORE, fragamt);
+			PlayerScore_Add(self.enemy, SP_DOM_TICKS, fragamt);
+		}
+		else
+			self.enemy = world;
+	}
+}
+
+void dompointtouch()
+{
+	entity head;
+	if (other.classname != "player")
+		return;
+	if (other.health < 1)
+		return;
+
+	if(time < self.captime + 0.3)
+		return;
+
+	// only valid teams can claim it
+	head = find(world, classname, "dom_team");
+	while (head && head.team != other.team)
+		head = find(head, classname, "dom_team");
+	if (!head || head.netname == "" || head == self.goalentity)
+		return;
+
+	// delay capture
+
+	self.team = self.goalentity.team; // this stores the PREVIOUS team!
+
+	self.cnt = other.team;
+	self.owner = head; // team to switch to after the delay
+	self.dmg_inflictor = other;
+
+	// self.state = 1;
+	// self.delay = time + cvar("g_domination_point_capturetime");
+	//self.nextthink = time + cvar("g_domination_point_capturetime");
+	//self.think = dompoint_captured;
+
+	// go to neutral team in the mean time
+	head = find(world, classname, "dom_team");
+	while (head && head.netname != "")
+		head = find(head, classname, "dom_team");
+	if(head == world)
+		return;
+
+	WaypointSprite_UpdateSprites(self.sprite, "dom-neut", "", "");
+	WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, '0 1 1');
+	WaypointSprite_Ping(self.sprite);
+
+	self.goalentity = head;
+	self.model = head.mdl;
+	self.modelindex = head.dmg;
+	self.skin = head.skin;
+
+	self.enemy = other; // individual player scoring
+	self.enemy_playerid = other.playerid;
+	dompoint_captured();
+}
+
+/*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
+Team declaration for Domination gameplay, this allows you to decide what team
+names and control point models are used in your map.
+
+Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
+can have netname set!  The nameless team owns all control points at start.
+
+Keys:
+"netname"
+ Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
+"cnt"
+ Scoreboard color of the team (for example 4 is red and 13 is blue)
+"model"
+ Model to use for control points owned by this team (for example
+ "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
+ keycard)
+"skin"
+ Skin of the model to use (for team skins on a single model)
+"noise"
+ Sound to play when this team captures a point.
+ (this is a localized sound, like a small alarm or other effect)
+"noise1"
+ Narrator speech to play when this team captures a point.
+ (this is a global sound, like "Red team has captured a control point")
+*/
+
+void spawnfunc_dom_team()
+{
+	if(!g_domination || autocvar_g_domination_teams_override >= 2)
+	{
+		remove(self);
+		return;
+	}
+	precache_model(self.model);
+	if (self.noise != "")
+		precache_sound(self.noise);
+	if (self.noise1 != "")
+		precache_sound(self.noise1);
+	self.classname = "dom_team";
+	setmodel(self, self.model); // precision not needed
+	self.mdl = self.model;
+	self.dmg = self.modelindex;
+	self.model = "";
+	self.modelindex = 0;
+	// this would have to be changed if used in quakeworld
+	if(self.cnt)
+		self.team = self.cnt + 1; // WHY are these different anyway?
+}
+
+void dom_controlpoint_setup()
+{
+	entity head;
+	// find the spawnfunc_dom_team representing unclaimed points
+	head = find(world, classname, "dom_team");
+	while(head && head.netname != "")
+		head = find(head, classname, "dom_team");
+	if (!head)
+		objerror("no spawnfunc_dom_team with netname \"\" found\n");
+
+	// copy important properties from spawnfunc_dom_team entity
+	self.goalentity = head;
+	setmodel(self, head.mdl); // precision already set
+	self.skin = head.skin;
+
+	self.cnt = -1;
+
+	if(self.message == "")
+		self.message = " has captured a control point";
+
+	if(self.DOMPOINTFRAGS <= 0)
+		self.DOMPOINTFRAGS = 1;
+	if(self.wait <= 0)
+		self.wait = 5;
+
+	float points, waittime;
+	if (autocvar_g_domination_point_amt)
+		points = autocvar_g_domination_point_amt;
+	else
+		points = self.frags;
+	if (autocvar_g_domination_point_rate)
+		waittime = autocvar_g_domination_point_rate;
+	else
+		waittime = self.wait;
+
+	total_pps += points/waittime;
+
+	if(!self.t_width)
+		self.t_width = 0.02; // frame animation rate
+	if(!self.t_length)
+		self.t_length = 239; // maximum frame
+
+	self.think = dompointthink;
+	self.nextthink = time;
+	self.touch = dompointtouch;
+	self.solid = SOLID_TRIGGER;
+	self.flags = FL_ITEM;
+	setsize(self, '-32 -32 -32', '32 32 32');
+	setorigin(self, self.origin + '0 0 20');
+	droptofloor();
+
+	waypoint_spawnforitem(self);
+	WaypointSprite_SpawnFixed("dom-neut", self.origin + '0 0 32', self, sprite, RADARICON_DOMPOINT, '0 1 1');
+}
+
+
+
+/*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
+Control point for Domination gameplay.
+*/
+void spawnfunc_dom_controlpoint()
+{
+	if(!g_domination)
+	{
+		remove(self);
+		return;
+	}
+	self.think = dom_controlpoint_setup;
+	self.nextthink = time + 0.1;
+	self.reset = dom_controlpoint_setup;
+
+	if(!self.scale)
+		self.scale = 0.6;
+
+	//if(!self.glow_size)
+	//	self.glow_size = cvar("g_domination_point_glow");
+	self.effects = self.effects | EF_LOWPRECISION;
+	if (autocvar_g_domination_point_fullbright)
+		self.effects |= EF_FULLBRIGHT;
+}
+
+// code from here on is just to support maps that don't have control point and team entities
+void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, string capsound, string capnarration, string capmessage)
+{
+	entity oldself;
+	oldself = self;
+	self = spawn();
+	self.classname = "dom_team";
+	self.netname = teamname;
+	self.cnt = teamcolor;
+	self.model = pointmodel;
+	self.skin = pointskin;
+	self.noise = capsound;
+	self.noise1 = capnarration;
+	self.message = capmessage;
+
+	// this code is identical to spawnfunc_dom_team
+	setmodel(self, self.model); // precision not needed
+	self.mdl = self.model;
+	self.dmg = self.modelindex;
+	self.model = "";
+	self.modelindex = 0;
+	// this would have to be changed if used in quakeworld
+	self.team = self.cnt + 1;
+
+	//eprint(self);
+	self = oldself;
+}
+
+void dom_spawnpoint(vector org)
+{
+	entity oldself;
+	oldself = self;
+	self = spawn();
+	self.classname = "dom_controlpoint";
+	self.think = spawnfunc_dom_controlpoint;
+	self.nextthink = time;
+	setorigin(self, org);
+	spawnfunc_dom_controlpoint();
+	self = oldself;
+}
+
+// spawn some default teams if the map is not set up for domination
+void dom_spawnteams()
+{
+	float numteams;
+	if(autocvar_g_domination_teams_override < 2)
+		numteams = autocvar_g_domination_default_teams;
+	else
+		numteams = autocvar_g_domination_teams_override;
+	// LordHavoc: edit this if you want to change defaults
+	dom_spawnteam("Red", COLOR_TEAM1-1, "models/domination/dom_red.md3", 0, "domination/claim.wav", "", "Red team has captured a control point");
+	dom_spawnteam("Blue", COLOR_TEAM2-1, "models/domination/dom_blue.md3", 0, "domination/claim.wav", "", "Blue team has captured a control point");
+	if(numteams > 2)
+		dom_spawnteam("Yellow", COLOR_TEAM3-1, "models/domination/dom_yellow.md3", 0, "domination/claim.wav", "", "Yellow team has captured a control point");
+	if(numteams > 3)
+		dom_spawnteam("Pink", COLOR_TEAM4-1, "models/domination/dom_pink.md3", 0, "domination/claim.wav", "", "Pink team has captured a control point");
+	dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, "", "", "");
+}
+
+void dom_delayedinit()
+{
+	entity head;
+
+	// if no teams are found, spawn defaults, if custom teams are set, use them
+	if (find(world, classname, "dom_team") == world || autocvar_g_domination_teams_override >= 2)
+		dom_spawnteams();
+	// if no control points are found, spawn defaults
+	if (find(world, classname, "dom_controlpoint") == world)
+	{
+		// TODO in a few months (maybe 2011/08): change this into error() and remove this very poor dom point selection
+		backtrace("This map contains no dom_controlpoint entities. A very poor dom point placement will be chosen. Please fix the map.");
+
+		// if no supported map was found, make every deathmatch spawn a point
+		head = find(world, classname, "info_player_deathmatch");
+		while (head)
+		{
+			dom_spawnpoint(head.origin);
+			head = find(head, classname, "info_player_deathmatch");
+		}
+	}
+
+	ScoreRules_dom();
+}
+
+void dom_init()
+{
+	// we have to precache default models/sounds even if they might not be
+	// used because spawnfunc_worldspawn is executed before any other entities are read,
+	// so we don't even know yet if this map is set up for domination...
+	precache_model("models/domination/dom_red.md3");
+	precache_model("models/domination/dom_blue.md3");
+	precache_model("models/domination/dom_yellow.md3");
+	precache_model("models/domination/dom_pink.md3");
+	precache_model("models/domination/dom_unclaimed.md3");
+	precache_sound("domination/claim.wav");
+	InitializeEntity(world, dom_delayedinit, INITPRIO_GAMETYPE);
+
+	addstat(STAT_DOM_TOTAL_PPS, AS_FLOAT, dom_total_pps);
+	addstat(STAT_DOM_PPS_RED, AS_FLOAT, dom_pps_red);
+	addstat(STAT_DOM_PPS_BLUE, AS_FLOAT, dom_pps_blue);
+	if(c3 >= 0) addstat(STAT_DOM_PPS_YELLOW, AS_FLOAT, dom_pps_yellow);
+	if(c4 >= 0) addstat(STAT_DOM_PPS_PINK, AS_FLOAT, dom_pps_pink);
+}
+
diff --git a/qcsrc/server/bot/havocbot/roles.qc b/qcsrc/server/bot/havocbot/roles.qc
index ad3bb2367..a3078c4e4 100644
--- a/qcsrc/server/bot/havocbot/roles.qc
+++ b/qcsrc/server/bot/havocbot/roles.qc
@@ -203,25 +203,6 @@ void havocbot_goalrating_enemyplayers(float ratingscale, vector org, float sradi
 // choose a role according to the situation
 void havocbot_role_dm();
 
-//DOM:
-//go to best items, or control points you don't own
-void havocbot_role_dom()
-{
-	if(self.deadflag != DEAD_NO)
-		return;
-
-	if (self.bot_strategytime < time)
-	{
-		self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
-		navigation_goalrating_start();
-		havocbot_goalrating_controlpoints(10000, self.origin, 15000);
-		havocbot_goalrating_items(8000, self.origin, 8000);
-		//havocbot_goalrating_enemyplayers(3000, self.origin, 2000);
-		//havocbot_goalrating_waypoints(1, self.origin, 1000);
-		navigation_goalrating_end();
-	}
-}
-
 //DM:
 //go to best items
 void havocbot_role_dm()
@@ -284,19 +265,12 @@ void havocbot_chooserole_race()
 	self.havocbot_role = havocbot_role_race;
 }
 
-void havocbot_chooserole_dom()
-{
-	self.havocbot_role = havocbot_role_dom;
-}
-
 void havocbot_chooserole()
 {
 	dprint("choosing a role...\n");
 	self.bot_strategytime = 0;
 	if (MUTATOR_CALLHOOK(HavocBot_ChooseRule))
 		return;
-	else if (g_domination)
-		havocbot_chooserole_dom();
 	else if (g_keyhunt)
 		havocbot_chooserole_kh();
 	else if (g_race || g_cts)
diff --git a/qcsrc/server/cl_client.qc b/qcsrc/server/cl_client.qc
index 165460eeb..0144fbf32 100644
--- a/qcsrc/server/cl_client.qc
+++ b/qcsrc/server/cl_client.qc
@@ -1381,9 +1381,6 @@ void ClientConnect (void)
 
 	race_PreSpawnObserver();
 
-	//if(g_domination)
-	//	dom_player_join_team(self);
-
 	// identify the right forced team
 	if(autocvar_g_campaign)
 	{
@@ -1569,9 +1566,6 @@ void ClientConnect (void)
 	else if(autocvar_sv_teamnagger && !(autocvar_bot_vs_human && (c3==-1 && c4==-1)) && !g_ca) // teamnagger is currently bad for ca
 		send_CSQC_teamnagger();
 
-	if (g_domination)
-		set_dom_state(self);
-
 	CheatInitClient();
 
 	if(!autocvar_g_campaign)
diff --git a/qcsrc/server/domination.qc b/qcsrc/server/domination.qc
deleted file mode 100644
index 6a0dcdf8b..000000000
--- a/qcsrc/server/domination.qc
+++ /dev/null
@@ -1,525 +0,0 @@
-
-/*
-Domination as a plugin for netquake mods
-by LordHavoc (lordhavoc@ghdigital.com)
-
-How to add domination points to a mod:
-1. Add this line to progs.src above world.qc:
-domination.qc
-2. Comment out all lines in ClientObituary in client.qc that begin with targ.frags  or attacker.frags.
-3. Add this above spawnfunc_worldspawn in world.qc:
-void() dom_init;
-4. Add this line to the end of spawnfunc_worldspawn in world.qc:
-dom_init();
-
-Note: The only teams who can use dom control points are identified by spawnfunc_dom_team entities (if none exist these default to red and blue and use only quake models/sounds).
-*/
-
-#define DOMPOINTFRAGS frags
-
-.float enemy_playerid;
-.entity sprite;
-.float captime;
-
-// pps: points per second
-.float dom_total_pps;
-.float dom_pps_red;
-.float dom_pps_blue;
-.float dom_pps_yellow;
-.float dom_pps_pink;
-float total_pps;
-float pps_red;
-float pps_blue;
-float pps_yellow;
-float pps_pink;
-void set_dom_state(entity e)
-{
-	e.dom_total_pps = total_pps;
-	e.dom_pps_red = pps_red;
-	e.dom_pps_blue = pps_blue;
-	if(c3 >= 0)
-		e.dom_pps_yellow = pps_yellow;
-	if(c4 >= 0)
-		e.dom_pps_pink = pps_pink;
-}
-
-void() dom_controlpoint_setup;
-
-void LogDom(string mode, float team_before, entity actor)
-{
-	string s;
-	if(!autocvar_sv_eventlog)
-		return;
-	s = strcat(":dom:", mode);
-	s = strcat(s, ":", ftos(team_before));
-	s = strcat(s, ":", ftos(actor.playerid));
-	GameLogEcho(s);
-}
-
-void() dom_spawnteams;
-
-void dompoint_captured ()
-{
-	entity head;
-	float old_delay, old_team, real_team;
-
-	// now that the delay has expired, switch to the latest team to lay claim to this point
-	head = self.owner;
-
-	real_team = self.cnt;
-	self.cnt = -1;
-
-	LogDom("taken", self.team, self.dmg_inflictor);
-	self.dmg_inflictor = world;
-
-	self.goalentity = head;
-	self.model = head.mdl;
-	self.modelindex = head.dmg;
-	self.skin = head.skin;
-
-	//bprint(head.message);
-	//bprint("\n");
-
-	//bprint(^3head.netname);
-	//bprint(head.netname);
-	//bprint(self.message);
-	//bprint("\n");
-
-	float points, wait_time;
-	if (autocvar_g_domination_point_amt)
-		points = autocvar_g_domination_point_amt;
-	else
-		points = self.frags;
-	if (autocvar_g_domination_point_rate)
-		wait_time = autocvar_g_domination_point_rate;
-	else
-		wait_time = self.wait;
-
-	bprint("^3", head.netname, "^3", self.message);
-	if (points != 1)
-		bprint(" ^7(", ftos(points), " points every ", ftos(wait_time), " seconds)\n");
-	else
-		bprint(" ^7(", ftos(points), " point every ", ftos(wait_time), " seconds)\n");
-
-	if(self.enemy.playerid == self.enemy_playerid)
-		PlayerScore_Add(self.enemy, SP_DOM_TAKES, 1);
-	else
-		self.enemy = world;
-
-	if (head.noise != "")
-		if(self.enemy)
-			sound(self.enemy, CH_TRIGGER, head.noise, VOL_BASE, ATTN_NORM);
-		else
-			sound(self, CH_TRIGGER, head.noise, VOL_BASE, ATTN_NORM);
-	if (head.noise1 != "")
-		play2all(head.noise1);
-
-	//self.nextthink = time + autocvar_g_domination_point_rate;
-	//self.think = dompointthink;
-
-	self.delay = time + wait_time;
-
-	// do trigger work
-	old_delay = self.delay;
-	old_team = self.team;
-	self.team = real_team;
-	self.delay = 0;
-	activator = self;
-	SUB_UseTargets ();
-	self.delay = old_delay;
-	self.team = old_team;
-
-	switch(self.goalentity.team)
-	{
-		case COLOR_TEAM1:
-			WaypointSprite_UpdateSprites(self.sprite, "dom-red", "", "");
-			break;
-		case COLOR_TEAM2:
-			WaypointSprite_UpdateSprites(self.sprite, "dom-blue", "", "");
-			break;
-		case COLOR_TEAM3:
-			WaypointSprite_UpdateSprites(self.sprite, "dom-yellow", "", "");
-			break;
-		case COLOR_TEAM4:
-			WaypointSprite_UpdateSprites(self.sprite, "dom-pink", "", "");
-	}
-
-	total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
-	for(head = world; (head = find(head, classname, "dom_controlpoint")) != world; )
-	{
-		if (autocvar_g_domination_point_amt)
-			points = autocvar_g_domination_point_amt;
-		else
-			points = head.frags;
-		if (autocvar_g_domination_point_rate)
-			wait_time = autocvar_g_domination_point_rate;
-		else
-			wait_time = head.wait;
-		switch(head.goalentity.team)
-		{
-			case COLOR_TEAM1:
-				pps_red += points/wait_time;
-				break;
-			case COLOR_TEAM2:
-				pps_blue += points/wait_time;
-				break;
-			case COLOR_TEAM3:
-				pps_yellow += points/wait_time;
-				break;
-			case COLOR_TEAM4:
-				pps_pink += points/wait_time;
-		}
-		total_pps += points/wait_time;
-	}
-
-	WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, colormapPaletteColor(self.goalentity.team - 1, 0));
-	WaypointSprite_Ping(self.sprite);
-
-	self.captime = time;
-
-	FOR_EACH_REALCLIENT(head)
-		set_dom_state(head);
-}
-
-void AnimateDomPoint()
-{
-	if(self.pain_finished > time)
-		return;
-	self.pain_finished = time + self.t_width;
-	if(self.nextthink > self.pain_finished)
-		self.nextthink = self.pain_finished;
-
-	self.frame = self.frame + 1;
-	if(self.frame > self.t_length)
-		self.frame = 0;
-}
-
-void dompointthink()
-{
-	float fragamt;
-
-	self.nextthink = time + 0.1;
-
-	//self.frame = self.frame + 1;
-	//if(self.frame > 119)
-	//	self.frame = 0;
-	AnimateDomPoint();
-
-	// give points
-
-	if (gameover || self.delay > time || time < game_starttime)	// game has ended, don't keep giving points
-		return;
-
-	if(autocvar_g_domination_point_rate)
-		self.delay = time + autocvar_g_domination_point_rate;
-	else
-		self.delay = time + self.wait;
-
-	// give credit to the team
-	// NOTE: this defaults to 0
-	if (self.goalentity.netname != "")
-	{
-		if(autocvar_g_domination_point_amt)
-			fragamt = autocvar_g_domination_point_amt;
-		else
-			fragamt = self.DOMPOINTFRAGS;
-		TeamScore_AddToTeam(self.goalentity.team, ST_SCORE, fragamt);
-		TeamScore_AddToTeam(self.goalentity.team, ST_DOM_TICKS, fragamt);
-
-		// give credit to the individual player, if he is still there
-		if (self.enemy.playerid == self.enemy_playerid)
-		{
-			PlayerScore_Add(self.enemy, SP_SCORE, fragamt);
-			PlayerScore_Add(self.enemy, SP_DOM_TICKS, fragamt);
-		}
-		else
-			self.enemy = world;
-	}
-}
-
-void dompointtouch()
-{
-	entity head;
-	if (other.classname != "player")
-		return;
-	if (other.health < 1)
-		return;
-
-	if(time < self.captime + 0.3)
-		return;
-
-	// only valid teams can claim it
-	head = find(world, classname, "dom_team");
-	while (head && head.team != other.team)
-		head = find(head, classname, "dom_team");
-	if (!head || head.netname == "" || head == self.goalentity)
-		return;
-
-	// delay capture
-
-	self.team = self.goalentity.team; // this stores the PREVIOUS team!
-
-	self.cnt = other.team;
-	self.owner = head; // team to switch to after the delay
-	self.dmg_inflictor = other;
-
-	// self.state = 1;
-	// self.delay = time + cvar("g_domination_point_capturetime");
-	//self.nextthink = time + cvar("g_domination_point_capturetime");
-	//self.think = dompoint_captured;
-
-	// go to neutral team in the mean time
-	head = find(world, classname, "dom_team");
-	while (head && head.netname != "")
-		head = find(head, classname, "dom_team");
-	if(head == world)
-		return;
-
-	WaypointSprite_UpdateSprites(self.sprite, "dom-neut", "", "");
-	WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, '0 1 1');
-	WaypointSprite_Ping(self.sprite);
-
-	self.goalentity = head;
-	self.model = head.mdl;
-	self.modelindex = head.dmg;
-	self.skin = head.skin;
-
-	self.enemy = other; // individual player scoring
-	self.enemy_playerid = other.playerid;
-	dompoint_captured();
-}
-
-/*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
-Team declaration for Domination gameplay, this allows you to decide what team
-names and control point models are used in your map.
-
-Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
-can have netname set!  The nameless team owns all control points at start.
-
-Keys:
-"netname"
- Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
-"cnt"
- Scoreboard color of the team (for example 4 is red and 13 is blue)
-"model"
- Model to use for control points owned by this team (for example
- "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
- keycard)
-"skin"
- Skin of the model to use (for team skins on a single model)
-"noise"
- Sound to play when this team captures a point.
- (this is a localized sound, like a small alarm or other effect)
-"noise1"
- Narrator speech to play when this team captures a point.
- (this is a global sound, like "Red team has captured a control point")
-*/
-
-void spawnfunc_dom_team()
-{
-	if(!g_domination || autocvar_g_domination_teams_override >= 2)
-	{
-		remove(self);
-		return;
-	}
-	precache_model(self.model);
-	if (self.noise != "")
-		precache_sound(self.noise);
-	if (self.noise1 != "")
-		precache_sound(self.noise1);
-	self.classname = "dom_team";
-	setmodel(self, self.model); // precision not needed
-	self.mdl = self.model;
-	self.dmg = self.modelindex;
-	self.model = "";
-	self.modelindex = 0;
-	// this would have to be changed if used in quakeworld
-	if(self.cnt)
-		self.team = self.cnt + 1; // WHY are these different anyway?
-}
-
-void dom_controlpoint_setup()
-{
-	entity head;
-	// find the spawnfunc_dom_team representing unclaimed points
-	head = find(world, classname, "dom_team");
-	while(head && head.netname != "")
-		head = find(head, classname, "dom_team");
-	if (!head)
-		objerror("no spawnfunc_dom_team with netname \"\" found\n");
-
-	// copy important properties from spawnfunc_dom_team entity
-	self.goalentity = head;
-	setmodel(self, head.mdl); // precision already set
-	self.skin = head.skin;
-
-	self.cnt = -1;
-
-	if(self.message == "")
-		self.message = " has captured a control point";
-
-	if(self.DOMPOINTFRAGS <= 0)
-		self.DOMPOINTFRAGS = 1;
-	if(self.wait <= 0)
-		self.wait = 5;
-
-	float points, waittime;
-	if (autocvar_g_domination_point_amt)
-		points = autocvar_g_domination_point_amt;
-	else
-		points = self.frags;
-	if (autocvar_g_domination_point_rate)
-		waittime = autocvar_g_domination_point_rate;
-	else
-		waittime = self.wait;
-
-	total_pps += points/waittime;
-
-	if(!self.t_width)
-		self.t_width = 0.02; // frame animation rate
-	if(!self.t_length)
-		self.t_length = 239; // maximum frame
-
-	self.think = dompointthink;
-	self.nextthink = time;
-	self.touch = dompointtouch;
-	self.solid = SOLID_TRIGGER;
-	self.flags = FL_ITEM;
-	setsize(self, '-32 -32 -32', '32 32 32');
-	setorigin(self, self.origin + '0 0 20');
-	droptofloor();
-
-	waypoint_spawnforitem(self);
-	WaypointSprite_SpawnFixed("dom-neut", self.origin + '0 0 32', self, sprite, RADARICON_DOMPOINT, '0 1 1');
-}
-
-
-
-/*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
-Control point for Domination gameplay.
-*/
-void spawnfunc_dom_controlpoint()
-{
-	if(!g_domination)
-	{
-		remove(self);
-		return;
-	}
-	self.think = dom_controlpoint_setup;
-	self.nextthink = time + 0.1;
-	self.reset = dom_controlpoint_setup;
-
-	if(!self.scale)
-		self.scale = 0.6;
-
-	//if(!self.glow_size)
-	//	self.glow_size = cvar("g_domination_point_glow");
-	self.effects = self.effects | EF_LOWPRECISION;
-	if (autocvar_g_domination_point_fullbright)
-		self.effects |= EF_FULLBRIGHT;
-}
-
-// code from here on is just to support maps that don't have control point and team entities
-void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, string capsound, string capnarration, string capmessage)
-{
-	entity oldself;
-	oldself = self;
-	self = spawn();
-	self.classname = "dom_team";
-	self.netname = teamname;
-	self.cnt = teamcolor;
-	self.model = pointmodel;
-	self.skin = pointskin;
-	self.noise = capsound;
-	self.noise1 = capnarration;
-	self.message = capmessage;
-
-	// this code is identical to spawnfunc_dom_team
-	setmodel(self, self.model); // precision not needed
-	self.mdl = self.model;
-	self.dmg = self.modelindex;
-	self.model = "";
-	self.modelindex = 0;
-	// this would have to be changed if used in quakeworld
-	self.team = self.cnt + 1;
-
-	//eprint(self);
-	self = oldself;
-}
-
-void dom_spawnpoint(vector org)
-{
-	entity oldself;
-	oldself = self;
-	self = spawn();
-	self.classname = "dom_controlpoint";
-	self.think = spawnfunc_dom_controlpoint;
-	self.nextthink = time;
-	setorigin(self, org);
-	spawnfunc_dom_controlpoint();
-	self = oldself;
-}
-
-// spawn some default teams if the map is not set up for domination
-void dom_spawnteams()
-{
-	float numteams;
-	if(autocvar_g_domination_teams_override < 2)
-		numteams = autocvar_g_domination_default_teams;
-	else
-		numteams = autocvar_g_domination_teams_override;
-	// LordHavoc: edit this if you want to change defaults
-	dom_spawnteam("Red", COLOR_TEAM1-1, "models/domination/dom_red.md3", 0, "domination/claim.wav", "", "Red team has captured a control point");
-	dom_spawnteam("Blue", COLOR_TEAM2-1, "models/domination/dom_blue.md3", 0, "domination/claim.wav", "", "Blue team has captured a control point");
-	if(numteams > 2)
-		dom_spawnteam("Yellow", COLOR_TEAM3-1, "models/domination/dom_yellow.md3", 0, "domination/claim.wav", "", "Yellow team has captured a control point");
-	if(numteams > 3)
-		dom_spawnteam("Pink", COLOR_TEAM4-1, "models/domination/dom_pink.md3", 0, "domination/claim.wav", "", "Pink team has captured a control point");
-	dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, "", "", "");
-}
-
-void dom_delayedinit()
-{
-	entity head;
-
-	// if no teams are found, spawn defaults, if custom teams are set, use them
-	if (find(world, classname, "dom_team") == world || autocvar_g_domination_teams_override >= 2)
-		dom_spawnteams();
-	// if no control points are found, spawn defaults
-	if (find(world, classname, "dom_controlpoint") == world)
-	{
-		// TODO in a few months (maybe 2011/08): change this into error() and remove this very poor dom point selection
-		backtrace("This map contains no dom_controlpoint entities. A very poor dom point placement will be chosen. Please fix the map.");
-
-		// if no supported map was found, make every deathmatch spawn a point
-		head = find(world, classname, "info_player_deathmatch");
-		while (head)
-		{
-			dom_spawnpoint(head.origin);
-			head = find(head, classname, "info_player_deathmatch");
-		}
-	}
-
-	ScoreRules_dom();
-}
-
-void dom_init()
-{
-	// we have to precache default models/sounds even if they might not be
-	// used because spawnfunc_worldspawn is executed before any other entities are read,
-	// so we don't even know yet if this map is set up for domination...
-	precache_model("models/domination/dom_red.md3");
-	precache_model("models/domination/dom_blue.md3");
-	precache_model("models/domination/dom_yellow.md3");
-	precache_model("models/domination/dom_pink.md3");
-	precache_model("models/domination/dom_unclaimed.md3");
-	precache_sound("domination/claim.wav");
-	InitializeEntity(world, dom_delayedinit, INITPRIO_GAMETYPE);
-
-	addstat(STAT_DOM_TOTAL_PPS, AS_FLOAT, dom_total_pps);
-	addstat(STAT_DOM_PPS_RED, AS_FLOAT, dom_pps_red);
-	addstat(STAT_DOM_PPS_BLUE, AS_FLOAT, dom_pps_blue);
-	if(c3 >= 0) addstat(STAT_DOM_PPS_YELLOW, AS_FLOAT, dom_pps_yellow);
-	if(c4 >= 0) addstat(STAT_DOM_PPS_PINK, AS_FLOAT, dom_pps_pink);
-}
-
diff --git a/qcsrc/server/g_world.qc b/qcsrc/server/g_world.qc
index 8d8c0328e..7912b2081 100644
--- a/qcsrc/server/g_world.qc
+++ b/qcsrc/server/g_world.qc
@@ -669,9 +669,6 @@ void spawnfunc_worldspawn (void)
 
 	WaypointSprite_Init();
 
-	//if (g_domination)
-	//	dom_init();
-
 	GameLogInit(); // prepare everything
 	// NOTE for matchid:
 	// changing the logic generating it is okay. But:
diff --git a/qcsrc/server/mutators/gamemode_domination.qc b/qcsrc/server/mutators/gamemode_domination.qc
new file mode 100644
index 000000000..852fb90c0
--- /dev/null
+++ b/qcsrc/server/mutators/gamemode_domination.qc
@@ -0,0 +1,515 @@
+void dom_EventLog(string mode, float team_before, entity actor) // use an alias for easy changing and quick editing later
+{
+	if(autocvar_sv_eventlog)
+		GameLogEcho(strcat(":dom:", mode, ":", ftos(team_before), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
+}
+
+void set_dom_state(entity e)
+{
+	e.dom_total_pps = total_pps;
+	e.dom_pps_red = pps_red;
+	e.dom_pps_blue = pps_blue;
+	if(c3 >= 0)
+		e.dom_pps_yellow = pps_yellow;
+	if(c4 >= 0)
+		e.dom_pps_pink = pps_pink;
+}
+
+void dompoint_captured ()
+{
+	entity head;
+	float old_delay, old_team, real_team;
+
+	// now that the delay has expired, switch to the latest team to lay claim to this point
+	head = self.owner;
+
+	real_team = self.cnt;
+	self.cnt = -1;
+
+	dom_EventLog("taken", self.team, self.dmg_inflictor);
+	self.dmg_inflictor = world;
+
+	self.goalentity = head;
+	self.model = head.mdl;
+	self.modelindex = head.dmg;
+	self.skin = head.skin;
+	
+	float points, wait_time;
+	if (autocvar_g_domination_point_amt)
+		points = autocvar_g_domination_point_amt;
+	else
+		points = self.frags;
+	if (autocvar_g_domination_point_rate)
+		wait_time = autocvar_g_domination_point_rate;
+	else
+		wait_time = self.wait;
+
+	bprint("^3", head.netname, "^3", self.message);
+	if (points != 1)
+		bprint(" ^7(", ftos(points), " points every ", ftos(wait_time), " seconds)\n");
+	else
+		bprint(" ^7(", ftos(points), " point every ", ftos(wait_time), " seconds)\n");
+
+	if(self.enemy.playerid == self.enemy_playerid)
+		PlayerScore_Add(self.enemy, SP_DOM_TAKES, 1);
+	else
+		self.enemy = world;
+
+	if (head.noise != "")
+		if(self.enemy)
+			sound(self.enemy, CH_TRIGGER, head.noise, VOL_BASE, ATTN_NORM);
+		else
+			sound(self, CH_TRIGGER, head.noise, VOL_BASE, ATTN_NORM);
+	if (head.noise1 != "")
+		play2all(head.noise1);
+
+	self.delay = time + wait_time;
+
+	// do trigger work
+	old_delay = self.delay;
+	old_team = self.team;
+	self.team = real_team;
+	self.delay = 0;
+	activator = self;
+	SUB_UseTargets ();
+	self.delay = old_delay;
+	self.team = old_team;
+
+	switch(self.goalentity.team)
+	{
+		case COLOR_TEAM1:
+			WaypointSprite_UpdateSprites(self.sprite, "dom-red", "", "");
+			break;
+		case COLOR_TEAM2:
+			WaypointSprite_UpdateSprites(self.sprite, "dom-blue", "", "");
+			break;
+		case COLOR_TEAM3:
+			WaypointSprite_UpdateSprites(self.sprite, "dom-yellow", "", "");
+			break;
+		case COLOR_TEAM4:
+			WaypointSprite_UpdateSprites(self.sprite, "dom-pink", "", "");
+	}
+
+	total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
+	for(head = world; (head = find(head, classname, "dom_controlpoint")) != world; )
+	{
+		if (autocvar_g_domination_point_amt)
+			points = autocvar_g_domination_point_amt;
+		else
+			points = head.frags;
+		if (autocvar_g_domination_point_rate)
+			wait_time = autocvar_g_domination_point_rate;
+		else
+			wait_time = head.wait;
+		switch(head.goalentity.team)
+		{
+			case COLOR_TEAM1:
+				pps_red += points/wait_time;
+				break;
+			case COLOR_TEAM2:
+				pps_blue += points/wait_time;
+				break;
+			case COLOR_TEAM3:
+				pps_yellow += points/wait_time;
+				break;
+			case COLOR_TEAM4:
+				pps_pink += points/wait_time;
+		}
+		total_pps += points/wait_time;
+	}
+
+	WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, colormapPaletteColor(self.goalentity.team - 1, 0));
+	WaypointSprite_Ping(self.sprite);
+
+	self.captime = time;
+
+	FOR_EACH_REALCLIENT(head)
+		set_dom_state(head);
+}
+
+void AnimateDomPoint()
+{
+	if(self.pain_finished > time)
+		return;
+	self.pain_finished = time + self.t_width;
+	if(self.nextthink > self.pain_finished)
+		self.nextthink = self.pain_finished;
+
+	self.frame = self.frame + 1;
+	if(self.frame > self.t_length)
+		self.frame = 0;
+}
+
+void dompointthink()
+{
+	float fragamt;
+
+	self.nextthink = time + 0.1;
+
+	//self.frame = self.frame + 1;
+	//if(self.frame > 119)
+	//	self.frame = 0;
+	AnimateDomPoint();
+
+	// give points
+
+	if (gameover || self.delay > time || time < game_starttime)	// game has ended, don't keep giving points
+		return;
+
+	if(autocvar_g_domination_point_rate)
+		self.delay = time + autocvar_g_domination_point_rate;
+	else
+		self.delay = time + self.wait;
+
+	// give credit to the team
+	// NOTE: this defaults to 0
+	if (self.goalentity.netname != "")
+	{
+		if(autocvar_g_domination_point_amt)
+			fragamt = autocvar_g_domination_point_amt;
+		else
+			fragamt = self.frags;
+		TeamScore_AddToTeam(self.goalentity.team, ST_SCORE, fragamt);
+		TeamScore_AddToTeam(self.goalentity.team, ST_DOM_TICKS, fragamt);
+
+		// give credit to the individual player, if he is still there
+		if (self.enemy.playerid == self.enemy_playerid)
+		{
+			PlayerScore_Add(self.enemy, SP_SCORE, fragamt);
+			PlayerScore_Add(self.enemy, SP_DOM_TICKS, fragamt);
+		}
+		else
+			self.enemy = world;
+	}
+}
+
+void dompointtouch()
+{
+	entity head;
+	if (other.classname != "player")
+		return;
+	if (other.health < 1)
+		return;
+
+	if(time < self.captime + 0.3)
+		return;
+
+	// only valid teams can claim it
+	head = find(world, classname, "dom_team");
+	while (head && head.team != other.team)
+		head = find(head, classname, "dom_team");
+	if (!head || head.netname == "" || head == self.goalentity)
+		return;
+
+	// delay capture
+
+	self.team = self.goalentity.team; // this stores the PREVIOUS team!
+
+	self.cnt = other.team;
+	self.owner = head; // team to switch to after the delay
+	self.dmg_inflictor = other;
+
+	// self.state = 1;
+	// self.delay = time + cvar("g_domination_point_capturetime");
+	//self.nextthink = time + cvar("g_domination_point_capturetime");
+	//self.think = dompoint_captured;
+
+	// go to neutral team in the mean time
+	head = find(world, classname, "dom_team");
+	while (head && head.netname != "")
+		head = find(head, classname, "dom_team");
+	if(head == world)
+		return;
+
+	WaypointSprite_UpdateSprites(self.sprite, "dom-neut", "", "");
+	WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, '0 1 1');
+	WaypointSprite_Ping(self.sprite);
+
+	self.goalentity = head;
+	self.model = head.mdl;
+	self.modelindex = head.dmg;
+	self.skin = head.skin;
+
+	self.enemy = other; // individual player scoring
+	self.enemy_playerid = other.playerid;
+	dompoint_captured();
+}
+
+void dom_controlpoint_setup()
+{
+	entity head;
+	// find the spawnfunc_dom_team representing unclaimed points
+	head = find(world, classname, "dom_team");
+	while(head && head.netname != "")
+		head = find(head, classname, "dom_team");
+	if (!head)
+		objerror("no spawnfunc_dom_team with netname \"\" found\n");
+
+	// copy important properties from spawnfunc_dom_team entity
+	self.goalentity = head;
+	setmodel(self, head.mdl); // precision already set
+	self.skin = head.skin;
+
+	self.cnt = -1;
+
+	if(self.message == "")
+		self.message = " has captured a control point";
+
+	if(self.frags <= 0)
+		self.frags = 1;
+	if(self.wait <= 0)
+		self.wait = 5;
+
+	float points, waittime;
+	if (autocvar_g_domination_point_amt)
+		points = autocvar_g_domination_point_amt;
+	else
+		points = self.frags;
+	if (autocvar_g_domination_point_rate)
+		waittime = autocvar_g_domination_point_rate;
+	else
+		waittime = self.wait;
+
+	total_pps += points/waittime;
+
+	if(!self.t_width)
+		self.t_width = 0.02; // frame animation rate
+	if(!self.t_length)
+		self.t_length = 239; // maximum frame
+
+	self.think = dompointthink;
+	self.nextthink = time;
+	self.touch = dompointtouch;
+	self.solid = SOLID_TRIGGER;
+	self.flags = FL_ITEM;
+	setsize(self, '-32 -32 -32', '32 32 32');
+	setorigin(self, self.origin + '0 0 20');
+	droptofloor();
+
+	waypoint_spawnforitem(self);
+	WaypointSprite_SpawnFixed("dom-neut", self.origin + '0 0 32', self, sprite, RADARICON_DOMPOINT, '0 1 1');
+}
+
+//go to best items, or control points you don't own
+void havocbot_role_dom()
+{
+	if(self.deadflag != DEAD_NO)
+		return;
+
+	if (self.bot_strategytime < time)
+	{
+		self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+		navigation_goalrating_start();
+		havocbot_goalrating_controlpoints(10000, self.origin, 15000);
+		havocbot_goalrating_items(8000, self.origin, 8000);
+		//havocbot_goalrating_enemyplayers(3000, self.origin, 2000);
+		//havocbot_goalrating_waypoints(1, self.origin, 1000);
+		navigation_goalrating_end();
+	}
+}
+
+MUTATOR_HOOKFUNCTION(dom_ClientConnect)
+{
+	set_dom_state(self);
+	return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(dom_BotRoles)
+{
+	self.havocbot_role = havocbot_role_dom;
+	return TRUE;
+}
+
+/*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
+Control point for Domination gameplay.
+*/
+void spawnfunc_dom_controlpoint()
+{
+	if(!g_domination)
+	{
+		remove(self);
+		return;
+	}
+	self.think = dom_controlpoint_setup;
+	self.nextthink = time + 0.1;
+	self.reset = dom_controlpoint_setup;
+
+	if(!self.scale)
+		self.scale = 0.6;
+
+	self.effects = self.effects | EF_LOWPRECISION;
+	if (autocvar_g_domination_point_fullbright)
+		self.effects |= EF_FULLBRIGHT;
+}
+
+/*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
+Team declaration for Domination gameplay, this allows you to decide what team
+names and control point models are used in your map.
+
+Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
+can have netname set!  The nameless team owns all control points at start.
+
+Keys:
+"netname"
+ Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
+"cnt"
+ Scoreboard color of the team (for example 4 is red and 13 is blue)
+"model"
+ Model to use for control points owned by this team (for example
+ "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
+ keycard)
+"skin"
+ Skin of the model to use (for team skins on a single model)
+"noise"
+ Sound to play when this team captures a point.
+ (this is a localized sound, like a small alarm or other effect)
+"noise1"
+ Narrator speech to play when this team captures a point.
+ (this is a global sound, like "Red team has captured a control point")
+*/
+
+void spawnfunc_dom_team()
+{
+	if(!g_domination || autocvar_g_domination_teams_override >= 2)
+	{
+		remove(self);
+		return;
+	}
+	precache_model(self.model);
+	if (self.noise != "")
+		precache_sound(self.noise);
+	if (self.noise1 != "")
+		precache_sound(self.noise1);
+	self.classname = "dom_team";
+	setmodel(self, self.model); // precision not needed
+	self.mdl = self.model;
+	self.dmg = self.modelindex;
+	self.model = "";
+	self.modelindex = 0;
+	// this would have to be changed if used in quakeworld
+	if(self.cnt)
+		self.team = self.cnt + 1; // WHY are these different anyway?
+}
+
+// scoreboard setup
+void ScoreRules_dom()
+{
+	float sp_domticks, sp_score;
+	sp_score = sp_domticks = 0;
+	if(autocvar_g_domination_disable_frags)
+		sp_domticks = SFL_SORT_PRIO_PRIMARY;
+	else
+		sp_score = SFL_SORT_PRIO_PRIMARY;
+	CheckAllowedTeams(world);
+	ScoreRules_basics(((c4>=0) ? 4 : (c3>=0) ? 3 : 2), sp_score, sp_score, TRUE);
+	ScoreInfo_SetLabel_TeamScore  (ST_DOM_TICKS,    "ticks",     sp_domticks);
+	ScoreInfo_SetLabel_PlayerScore(SP_DOM_TICKS,    "ticks",     sp_domticks);
+	ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES,    "takes",     0);
+	ScoreRules_basics_end();
+}
+
+// code from here on is just to support maps that don't have control point and team entities
+void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, string capsound, string capnarration, string capmessage)
+{
+	entity oldself;
+	oldself = self;
+	self = spawn();
+	self.classname = "dom_team";
+	self.netname = teamname;
+	self.cnt = teamcolor;
+	self.model = pointmodel;
+	self.skin = pointskin;
+	self.noise = capsound;
+	self.noise1 = capnarration;
+	self.message = capmessage;
+
+	// this code is identical to spawnfunc_dom_team
+	setmodel(self, self.model); // precision not needed
+	self.mdl = self.model;
+	self.dmg = self.modelindex;
+	self.model = "";
+	self.modelindex = 0;
+	// this would have to be changed if used in quakeworld
+	self.team = self.cnt + 1;
+
+	//eprint(self);
+	self = oldself;
+}
+
+void dom_spawnpoint(vector org)
+{
+	entity oldself;
+	oldself = self;
+	self = spawn();
+	self.classname = "dom_controlpoint";
+	self.think = spawnfunc_dom_controlpoint;
+	self.nextthink = time;
+	setorigin(self, org);
+	spawnfunc_dom_controlpoint();
+	self = oldself;
+}
+
+// spawn some default teams if the map is not set up for domination
+void dom_spawnteams()
+{
+	float numteams = ((autocvar_g_domination_teams_override < 2) ? autocvar_g_domination_default_teams : autocvar_g_domination_teams_override);
+
+	dom_spawnteam("Red", COLOR_TEAM1-1, "models/domination/dom_red.md3", 0, "domination/claim.wav", "", "Red team has captured a control point");
+	dom_spawnteam("Blue", COLOR_TEAM2-1, "models/domination/dom_blue.md3", 0, "domination/claim.wav", "", "Blue team has captured a control point");
+	if(numteams > 2)
+		dom_spawnteam("Yellow", COLOR_TEAM3-1, "models/domination/dom_yellow.md3", 0, "domination/claim.wav", "", "Yellow team has captured a control point");
+	if(numteams > 3)
+		dom_spawnteam("Pink", COLOR_TEAM4-1, "models/domination/dom_pink.md3", 0, "domination/claim.wav", "", "Pink team has captured a control point");
+	dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, "", "", "");
+}
+
+void dom_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
+{
+	// if no teams are found, spawn defaults
+	if(find(world, classname, "dom_team") == world || autocvar_g_domination_teams_override >= 2)
+	{
+		print("No ""dom_team"" entities found on this map, creating them anyway.\n");
+		dom_spawnteams();
+	}
+	
+	ScoreRules_dom();
+}
+
+void dom_Initialize()
+{
+	precache_model("models/domination/dom_red.md3");
+	precache_model("models/domination/dom_blue.md3");
+	precache_model("models/domination/dom_yellow.md3");
+	precache_model("models/domination/dom_pink.md3");
+	precache_model("models/domination/dom_unclaimed.md3");
+	precache_sound("domination/claim.wav");
+	
+	addstat(STAT_DOM_TOTAL_PPS, AS_FLOAT, dom_total_pps);
+	addstat(STAT_DOM_PPS_RED, AS_FLOAT, dom_pps_red);
+	addstat(STAT_DOM_PPS_BLUE, AS_FLOAT, dom_pps_blue);
+	if(c3 >= 0) addstat(STAT_DOM_PPS_YELLOW, AS_FLOAT, dom_pps_yellow);
+	if(c4 >= 0) addstat(STAT_DOM_PPS_PINK, AS_FLOAT, dom_pps_pink);
+	
+	InitializeEntity(world, dom_DelayedInit, INITPRIO_GAMETYPE);
+}
+
+
+MUTATOR_DEFINITION(gamemode_domination)
+{
+	MUTATOR_HOOK(ClientConnect, dom_ClientConnect, CBC_ORDER_ANY);
+	MUTATOR_HOOK(HavocBot_ChooseRule, dom_BotRoles, 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.");
+		dom_Initialize();
+	}
+
+	MUTATOR_ONREMOVE
+	{
+		error("This is a game type and it cannot be removed at runtime.");
+	}
+
+	return 0;
+}
diff --git a/qcsrc/server/mutators/gamemode_domination.qh b/qcsrc/server/mutators/gamemode_domination.qh
new file mode 100644
index 000000000..a7d18530c
--- /dev/null
+++ b/qcsrc/server/mutators/gamemode_domination.qh
@@ -0,0 +1,23 @@
+// these are needed since mutators are compiled last
+
+// score rule declarations
+#define ST_DOM_TICKS 1
+#define SP_DOM_TICKS 4
+#define SP_DOM_TAKES 5
+
+// pps: points per second
+.float dom_total_pps;
+.float dom_pps_red;
+.float dom_pps_blue;
+.float dom_pps_yellow;
+.float dom_pps_pink;
+float total_pps;
+float pps_red;
+float pps_blue;
+float pps_yellow;
+float pps_pink;
+
+// capture declarations
+.float enemy_playerid;
+.entity sprite;
+.float captime;
\ No newline at end of file
diff --git a/qcsrc/server/mutators/mutators.qh b/qcsrc/server/mutators/mutators.qh
index 2ac6094d3..f6327a163 100644
--- a/qcsrc/server/mutators/mutators.qh
+++ b/qcsrc/server/mutators/mutators.qh
@@ -4,6 +4,7 @@ MUTATOR_DECLARATION(gamemode_keepaway);
 MUTATOR_DECLARATION(gamemode_ctf);
 MUTATOR_DECLARATION(gamemode_nexball);
 MUTATOR_DECLARATION(gamemode_onslaught);
+MUTATOR_DECLARATION(gamemode_domination);
 
 MUTATOR_DECLARATION(mutator_dodging);
 MUTATOR_DECLARATION(mutator_invincibleprojectiles);
diff --git a/qcsrc/server/progs.src b/qcsrc/server/progs.src
index 028519372..d04d5ac70 100644
--- a/qcsrc/server/progs.src
+++ b/qcsrc/server/progs.src
@@ -30,6 +30,7 @@ defs.qh		// Should rename this, it has fields and globals
 mutators/base.qh
 mutators/mutators.qh
 mutators/gamemode_ctf.qh
+mutators/gamemode_domination.qh
 mutators/gamemode_keyhunt.qh // TODO fix this
 mutators/gamemode_keepaway.qh
 mutators/gamemode_nexball.qh 
@@ -141,7 +142,7 @@ t_plats.qc
 antilag.qc
 
 //ctf.qc
-domination.qc
+//domination.qc
 //mode_onslaught.qc
 //nexball.qc
 g_hook.qc
@@ -208,6 +209,7 @@ playerstats.qc
 
 mutators/base.qc
 mutators/gamemode_ctf.qc
+mutators/gamemode_domination.qc
 mutators/gamemode_freezetag.qc
 mutators/gamemode_keyhunt.qc
 mutators/gamemode_keepaway.qc
diff --git a/qcsrc/server/scores_rules.qc b/qcsrc/server/scores_rules.qc
index c4021fc39..ebf133688 100644
--- a/qcsrc/server/scores_rules.qc
+++ b/qcsrc/server/scores_rules.qc
@@ -44,26 +44,6 @@ void ScoreRules_generic()
 	ScoreRules_basics_end();
 }
 
-// g_domination
-#define ST_DOM_TICKS 1
-#define SP_DOM_TICKS 4
-#define SP_DOM_TAKES 5
-void ScoreRules_dom()
-{
-	float sp_domticks, sp_score;
-	sp_score = sp_domticks = 0;
-	if(autocvar_g_domination_disable_frags)
-		sp_domticks = SFL_SORT_PRIO_PRIMARY;
-	else
-		sp_score = SFL_SORT_PRIO_PRIMARY;
-	CheckAllowedTeams(world);
-	ScoreRules_basics(((c4>=0) ? 4 : (c3>=0) ? 3 : 2), sp_score, sp_score, TRUE);
-	ScoreInfo_SetLabel_TeamScore  (ST_DOM_TICKS,    "ticks",     sp_domticks);
-	ScoreInfo_SetLabel_PlayerScore(SP_DOM_TICKS,    "ticks",     sp_domticks);
-	ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES,    "takes",     0);
-	ScoreRules_basics_end();
-}
-
 // LMS stuff
 #define SP_LMS_LIVES 4
 #define SP_LMS_RANK 5
diff --git a/qcsrc/server/teamplay.qc b/qcsrc/server/teamplay.qc
index b8f2f3ac8..1ed2f533b 100644
--- a/qcsrc/server/teamplay.qc
+++ b/qcsrc/server/teamplay.qc
@@ -52,7 +52,6 @@ string TeamNoName(float t)
 	return "Neutral Team";
 }
 
-void dom_init();
 void runematch_init();
 void tdm_init();
 void entcs_init();
@@ -134,7 +133,7 @@ void InitGameplayMode()
 		ActivateTeamplay();
 		fraglimit_override = autocvar_g_domination_point_limit;
 		leadlimit_override = autocvar_g_domination_point_leadlimit;
-		dom_init();
+		MUTATOR_ADD(gamemode_domination);
 		have_team_spawns = -1; // request team spawns
 	}