r5515 - in trunk/data/qcsrc/server: . monsters

DONOTREPLY at icculus.org DONOTREPLY at icculus.org
Wed Jan 14 04:07:43 EST 2009


Author: div0
Date: 2009-01-14 04:07:30 -0500 (Wed, 14 Jan 2009)
New Revision: 5515

Added:
   trunk/data/qcsrc/server/monsters/
   trunk/data/qcsrc/server/monsters/ai.qc
   trunk/data/qcsrc/server/monsters/defs.qc
   trunk/data/qcsrc/server/monsters/fight.qc
   trunk/data/qcsrc/server/monsters/m_monsters.qc
   trunk/data/qcsrc/server/monsters/mode_management.qc
Modified:
   trunk/data/qcsrc/server/progs.src
   trunk/data/qcsrc/server/t_teleporters.qc
Log:
monster framework from dpmod


Added: trunk/data/qcsrc/server/monsters/ai.qc
===================================================================
--- trunk/data/qcsrc/server/monsters/ai.qc	                        (rev 0)
+++ trunk/data/qcsrc/server/monsters/ai.qc	2009-01-14 09:07:30 UTC (rev 5515)
@@ -0,0 +1,885 @@
+void() movetarget_f;
+void() t_movetarget;
+void() FoundTarget;
+
+float MONSTER_WANDER = 64; // disable wandering around
+float MONSTER_APPEAR = 128; // spawn invisible, and appear when triggered
+
+.float ismonster;
+.float monsterawaitingteleport; // avoid awaking monsters in teleport rooms
+
+// when a monster becomes angry at a player, that monster will be used
+// as the sight target the next frame so that monsters near that one
+// will wake up even if they wouldn't have noticed the player
+//
+entity sight_entity;
+float sight_entity_time;
+
+/*
+
+.enemy
+Will be world if not currently angry at anyone.
+
+.movetarget
+The next path spot to walk toward.  If .enemy, ignore .movetarget.
+When an enemy is killed, the monster will try to return to it's path.
+
+.huntt_ime
+Set to time + something when the player is in sight, but movement straight for
+him is blocked.  This causes the monster to use wall following code for
+movement direction instead of sighting on the player.
+
+.ideal_yaw
+A yaw angle of the intended direction, which will be turned towards at up
+to 45 deg / state.  If the enemy is in view and hunt_time is not active,
+this will be the exact line towards the enemy.
+
+.pausetime
+A monster will leave it's stand state and head towards it's .movetarget when
+time > .pausetime.
+
+walkmove(angle, speed) primitive is all or nothing
+*/
+
+
+//
+// globals
+//
+//float current_yaw;
+
+float(float v) anglemod =
+{
+	v = v - 360 * floor(v / 360);
+	return v;
+};
+
+/*
+==============================================================================
+
+MOVETARGET CODE
+
+The angle of the movetarget effects standing and bowing direction, but has no effect on movement, which allways heads to the next target.
+
+targetname
+must be present.  The name of this movetarget.
+
+target
+the next spot to move to.  If not present, stop here for good.
+
+pausetime
+The number of seconds to spend standing or bowing for path_stand or path_bow
+
+==============================================================================
+*/
+
+
+void() movetarget_f =
+{
+	if (!self.targetname)
+		objerror ("monster_movetarget: no targetname");
+
+	self.solid = SOLID_TRIGGER;
+	self.touch = t_movetarget;
+	setsize (self, '-8 -8 -8', '8 8 8');
+};
+
+/*QUAKED path_corner (0.5 0.3 0) (-8 -8 -8) (8 8 8)
+Monsters will continue walking towards the next target corner.
+*/
+void() path_corner =
+{
+	movetarget_f ();
+};
+
+/*
+=============
+t_movetarget
+
+Something has bumped into a movetarget.  If it is a monster
+moving towards it, change the next destination and continue.
+==============
+*/
+void() t_movetarget =
+{
+	local entity temp;
+
+	if (other.health < 1)
+		return;
+	if (other.movetarget != self)
+		return;
+
+	if (other.enemy)
+		return;		// fighting, not following a path
+
+	temp = self;
+	self = other;
+	other = temp;
+
+	if (self.classname == "monster_ogre")
+		sound (self, CHAN_VOICE, "ogre/ogdrag.wav", 1, ATTN_IDLE);// play chainsaw drag sound
+
+//dprint ("t_movetarget\n");
+	self.goalentity = self.movetarget = find (world, targetname, other.target);
+	self.ideal_yaw = vectoyaw(self.goalentity.origin - self.origin);
+	if (!self.movetarget)
+	{
+		self.pausetime = time + 999999;
+		self.th_stand ();
+		return;
+	}
+};
+
+void() monster_wanderpaththink =
+{
+	local vector v, v1;
+	local float b, c;
+	self.nextthink = time + random() * 10 + 1;
+	if (self.owner.health < 1) // dead, also handled in death code
+	{
+		self.owner.movetarget = world;
+		remove(self);
+		return;
+	}
+	b = -1;
+	c = 10;
+	while (c > 0)
+	{
+		c = c - 1;
+		v = randomvec();
+		traceline(self.owner.origin, v * 1024 + self.owner.origin, FALSE, self);
+		v = trace_endpos - (normalize(v) * 16) - self.owner.origin;
+		if (vlen(v) > b)
+		{
+			b = vlen(v);
+			v1 = v;
+		}
+	}
+	setorigin(self, v1 + self.owner.origin);
+	self.owner.ideal_yaw = vectoyaw(self.origin - self.owner.origin);
+};
+
+void() monster_wanderpathtouch =
+{
+	if (other.health < 1)
+		return;
+	if (other.movetarget != self)
+		return;
+
+	if (other.enemy)
+		return;		// fighting, not following a path
+
+	if (other.classname == "monster_ogre")
+		sound (other, CHAN_VOICE, "ogre/ogdrag.wav", 1, ATTN_IDLE);// play chainsaw drag sound
+	monster_wanderpaththink();
+};
+
+void() monster_spawnwanderpath =
+{
+	newmis = spawn();
+	newmis.classname = "monster_wanderpath";
+	newmis.solid = SOLID_TRIGGER;
+	newmis.touch = monster_wanderpathtouch;
+	setsize (newmis, '-8 -8 -8', '8 8 8');
+	newmis.think = monster_wanderpaththink;
+	newmis.nextthink = time + random() * 10 + 1;
+	newmis.owner = self;
+	self.goalentity = self.movetarget = newmis;
+};
+
+void() monster_checkbossflag =
+{
+	local float healthboost;
+	local float r;
+
+#if 0
+	// monsterbosses cvar or spawnflag 64 causes a monster to be a miniboss
+	if ((self.spawnflags & 64) || (random() * 100 < cvar("monsterbosspercent")))
+	{
+		self.radsuit_finished = time + 1000000000;
+		r = random() * 4;
+		if (r < 2)
+		{
+			self.super_damage_finished = time + 1000000000;
+			healthboost = 30 + self.health * 0.5;
+			self.effects = self.effects | (EF_FULLBRIGHT | EF_BLUE);
+		}
+		if (r >= 1)
+		{
+			healthboost = 30 + self.health * bound(0.5, skill * 0.5, 1.5);
+			self.effects = self.effects | (EF_FULLBRIGHT | EF_RED);
+			self.healthregen = max(self.healthregen, min(skill * 10, 30));
+		}
+		self.health = self.health + healthboost;
+		self.max_health = self.health;
+		self.bodyhealth = self.bodyhealth * 2 + healthboost;
+		do
+		{
+			self.colormod_x = random();
+			self.colormod_y = random();
+			self.colormod_z = random();
+			self.colormod =  normalize(self.colormod);
+		}
+		while (self.colormod_x > 0.6 && self.colormod_y > 0.6 && self.colormod_z > 0.6);
+	}
+#endif
+};
+
+
+//============================================================================
+
+/*
+=============
+range
+
+returns the range catagorization of an entity reletive to self
+0	melee range, will become hostile even if back is turned
+1	visibility and infront, or visibility and show hostile
+2	infront and show hostile
+3	only triggered by damage
+=============
+*/
+float(entity targ) range =
+{
+	local float r;
+	r = vlen ((self.origin + self.view_ofs) - (targ.origin + targ.view_ofs));
+	if (r < 120)
+		return RANGE_MELEE;
+	if (r < 500)
+		return RANGE_NEAR;
+	if (r < 2000) // increased from 1000 for DP
+		return RANGE_MID;
+	return RANGE_FAR;
+};
+
+/*
+=============
+visible
+
+returns 1 if the entity is visible to self, even if not infront ()
+=============
+*/
+float (entity targ) visible =
+{
+	if (vlen(targ.origin - self.origin) > 5000) // long traces are slow
+		return FALSE;
+
+	traceline ((self.origin + self.view_ofs), (targ.origin + targ.view_ofs), TRUE, self);   // see through other monsters
+
+	if (trace_inopen && trace_inwater)
+		return FALSE;			// sight line crossed contents
+
+	if (trace_fraction == 1)
+		return TRUE;
+	return FALSE;
+};
+
+
+/*
+=============
+infront
+
+returns 1 if the entity is in front (in sight) of self
+=============
+*/
+float(entity targ) infront =
+{
+	local float dot;
+
+	makevectors (self.angles);
+	dot = normalize (targ.origin - self.origin) * v_forward;
+
+	return (dot > 0.3);
+};
+// returns 0 if not infront, or the dotproduct if infront
+float(vector dir, entity targ) infront2 =
+{
+	local float dot;
+
+	dir = normalize(dir);
+	dot = normalize (targ.origin - self.origin) * dir;
+
+	if (dot >= 0.3) return dot; // infront
+	return 0;
+};
+
+
+//============================================================================
+
+/*
+===========
+ChangeYaw
+
+Turns towards self.ideal_yaw at self.yaw_speed
+Sets the global variable current_yaw
+Called every 0.1 sec by monsters
+============
+*/
+/*
+
+void() ChangeYaw =
+{
+	local float ideal, move;
+
+//current_yaw = self.ideal_yaw;
+// mod down the current angle
+	current_yaw = anglemod( self.angles_y );
+	ideal = self.ideal_yaw;
+
+	if (current_yaw == ideal)
+		return;
+
+	move = ideal - current_yaw;
+	if (ideal > current_yaw)
+	{
+		if (move > 180)
+			move = move - 360;
+	}
+	else
+	{
+		if (move < -180)
+			move = move + 360;
+	}
+
+	if (move > 0)
+	{
+		if (move > self.yaw_speed)
+			move = self.yaw_speed;
+	}
+	else
+	{
+		if (move < 0-self.yaw_speed )
+			move = 0-self.yaw_speed;
+	}
+
+	current_yaw = anglemod (current_yaw + move);
+
+	self.angles_y = current_yaw;
+};
+
+*/
+
+
+//============================================================================
+
+void() HuntTarget =
+{
+	self.goalentity = self.enemy;
+	self.think = self.th_run;
+	self.ideal_yaw = vectoyaw(self.enemy.origin - self.origin);
+	self.nextthink = time + 0.1;
+	SUB_AttackFinished (1);	// wait a while before first attack
+};
+
+.void() th_sightsound;
+
+void() SightSound =
+{
+	if (self.health < 1)
+		return;
+	// skill 5 does not play sight sounds, instead you only hear the appear sound as they are about to attack
+	if (skill >= 5)
+	if (self.classname != "monster_hellfish")
+		return;
+
+	if (self.th_sightsound)
+		self.th_sightsound();
+};
+
+void() FoundTarget =
+{
+	if (self.health < 1 || !self.th_run)
+		return;
+	if (self.enemy.health < 1 || !self.enemy.takedamage)
+		return;
+	if (self.enemy.classname == "player")
+	{
+		// let other monsters see this monster for a while
+		sight_entity = self;
+		sight_entity_time = time + 0.1;
+	}
+
+	self.show_hostile = time + 1;		// wake up other monsters
+
+	SightSound ();
+	HuntTarget ();
+};
+
+/*
+//float checkplayertime;
+entity lastcheckplayer;
+entity havocbot_list;
+
+
+entity() checkplayer =
+{
+	local entity check;
+	local float worldcount;
+	// we can just fallback on checkclient if there are no bots
+	if (!havocbot_list)
+		return checkclient();
+*/
+	/*
+	if (time < checkplayertime)
+	{
+		traceline(self.origin + self.view_ofs, lastcheckplayer.origin + lastcheckplayer.view_ofs, TRUE, self);
+		if (trace_fraction == 1)
+			return lastcheckplayer;
+		if (trace_ent == lastcheckplayer)
+			return lastcheckplayer;
+	}
+	checkplayertime = time + 0.1;
+	*/
+/*
+	check = lastcheckplayer;
+	worldcount = 0;
+	c = 0;
+	do
+	{
+		c = c + 1;
+		check = findfloat(check, havocattack, TRUE);
+		if (check.classname == "player" || check.classname == "turretbase")
+		{
+			traceline(self.origin + self.view_ofs, check.origin + check.view_ofs, TRUE, self);
+			if (trace_fraction == 1)
+				return lastcheckplayer = check;
+			if (trace_ent == check)
+				return lastcheckplayer = check;
+		}
+		else if (check == world)
+		{
+			worldcount = worldcount + 1;
+			if (worldcount >= 2)
+				return lastcheckplayer = check;
+		}
+	}
+	while(check != lastcheckplayer && c < 100);
+	return world;
+};
+*/
+
+/*
+===========
+FindTarget
+
+Self is currently not attacking anything, so try to find a target
+
+Returns TRUE if an enemy was sighted
+
+When a player fires a missile, the point of impact becomes a fakeplayer so
+that monsters that see the impact will respond as if they had seen the
+player.
+
+To avoid spending too much time, only a single client (or fakeclient) is
+checked each frame.  This means multi player games will have slightly
+slower noticing monsters.
+============
+*/
+.float findtarget;
+float() FindTarget =
+{
+	local entity client;
+	local float r;
+
+	if (self.health < 1)
+		return FALSE;
+
+	// if the first or second spawnflag bit is set, the monster will only
+	// wake up on really seeing the player, not another monster getting angry
+
+	if (self.spawnflags & 3)
+	{
+		// don't wake up on seeing another monster getting angry
+		client = checkclient ();
+		if (!client)
+			return FALSE;   // current check entity isn't in PVS
+	}
+	else
+	{
+		if (sight_entity_time >= time)
+		{
+			client = sight_entity;
+			if (client.enemy == self.enemy)
+				return TRUE;
+		}
+		else
+		{
+			client = checkclient ();
+			if (!client)
+				return FALSE;   // current check entity isn't in PVS
+		}
+	}
+
+	if (client == self.enemy)
+		return FALSE;
+
+	if (client.flags & FL_NOTARGET)
+		return FALSE;
+
+#if 0
+	if (client.items & IT_INVISIBILITY)
+		return FALSE;
+#endif
+
+	// on skill 5 the monsters usually ignore the player and remain ghostlike
+	if (skill >= 5)
+	if (self.classname != "monster_hellfish")
+	if (random() < 0.99)
+		return FALSE;
+
+	r = range(client);
+	if (r == RANGE_FAR)
+		return FALSE;
+
+	if (!visible (client))
+		return FALSE;
+
+	if (r == RANGE_NEAR)
+	{
+		if (client.show_hostile < time && !infront (client))
+			return FALSE;
+	}
+	else if (r == RANGE_MID)
+	{
+		// LordHavoc: was if ( /* client.show_hostile < time || */ !infront (client))
+		if (client.show_hostile < time && !infront (client))
+			return FALSE;
+	}
+
+	//
+	// got one
+	//
+
+	if (client.model == "")
+		return FALSE;
+	self.enemy = client;
+	if (self.enemy.classname != "player" && self.enemy.classname != "turretbase")
+	{
+		self.enemy = self.enemy.enemy;
+		if (self.enemy.classname != "player" && self.enemy.classname != "turretbase")
+		{
+			self.enemy = world;
+			return FALSE;
+		}
+	}
+
+	FoundTarget ();
+
+	return TRUE;
+};
+
+
+//=============================================================================
+
+void(float dist) ai_forward =
+{
+	walkmove (self.angles_y, dist);
+};
+
+void(float dist) ai_back =
+{
+	walkmove ( (self.angles_y+180), dist);
+};
+
+
+void(float a) monster_setalpha;
+
+/*
+=============
+ai_pain
+
+stagger back a bit
+=============
+*/
+void(float dist) ai_pain =
+{
+	if (self.health < 1)
+		return;
+	ai_back (dist);
+};
+
+/*
+=============
+ai_painforward
+
+stagger back a bit
+=============
+*/
+void(float dist) ai_painforward =
+{
+	if (self.health < 1)
+		return;
+	walkmove (self.ideal_yaw, dist);
+};
+
+/*
+=============
+ai_walk
+
+The monster is walking it's beat
+=============
+*/
+void(float dist) ai_walk =
+{
+	if (self.health < 1)
+		return;
+
+	movedist = dist;
+
+	// check for noticing a player
+	if (self.oldenemy.takedamage)
+	if (self.oldenemy.health >= 1)
+	{
+		self.enemy = self.oldenemy;
+		self.oldenemy = world;
+		FoundTarget();
+		monster_setalpha(0);
+		return;
+	}
+	if (self.enemy)
+	{
+		if (self.enemy.takedamage)
+		{
+			if (self.enemy.health >= 1)
+			{
+				FoundTarget();
+				monster_setalpha(0);
+				return;
+			}
+			else
+				self.enemy = world;
+		}
+		else
+			self.enemy = world;
+	}
+
+	self.findtarget = TRUE;
+
+	movetogoal (dist);
+	monster_setalpha(0);
+};
+
+
+/*
+=============
+ai_stand
+
+The monster is staying in one place for a while, with slight angle turns
+=============
+*/
+void() ai_stand =
+{
+	if (self.health < 1)
+		return;
+	if (self.enemy)
+	{
+		if (self.enemy.takedamage)
+		{
+			if (self.enemy.health >= 1)
+			{
+				FoundTarget();
+				monster_setalpha(0);
+				return;
+			}
+			else
+				self.enemy = world;
+		}
+		else
+			self.enemy = world;
+	}
+	self.findtarget = TRUE;
+
+	if (time > self.pausetime)
+	{
+		self.th_walk ();
+		monster_setalpha(0);
+		return;
+	}
+
+// change angle slightly
+
+	monster_setalpha(0);
+};
+
+/*
+=============
+ai_turn
+
+don't move, but turn towards ideal_yaw
+=============
+*/
+void() ai_turn =
+{
+	if (self.enemy)
+	{
+		if (self.enemy.takedamage)
+		{
+			if (self.enemy.health >= 1)
+			{
+				FoundTarget();
+				monster_setalpha(0);
+				return;
+			}
+			else
+				self.enemy = world;
+		}
+		else
+			self.enemy = world;
+	}
+	self.findtarget = TRUE;
+
+	ChangeYaw ();
+	monster_setalpha(0);
+};
+
+//=============================================================================
+
+/*
+=============
+ChooseTurn
+=============
+*/
+void(vector pDestvec) ChooseTurn =
+{
+	local vector dir, newdir;
+
+	dir = self.origin - pDestvec;
+
+	newdir_x = trace_plane_normal_y;
+	newdir_y = 0 - trace_plane_normal_x;
+	newdir_z = 0;
+
+	if (dir * newdir > 0)
+	{
+		dir_x = 0 - trace_plane_normal_y;
+		dir_y = trace_plane_normal_x;
+	}
+	else
+	{
+		dir_x = trace_plane_normal_y;
+		dir_y = 0 - trace_plane_normal_x;
+	}
+
+	dir_z = 0;
+	self.ideal_yaw = vectoyaw(dir);
+};
+
+/*
+============
+FacingIdeal
+
+============
+*/
+float() FacingIdeal =
+{
+	local float delta;
+
+	delta = anglemod(self.angles_y - self.ideal_yaw);
+	if (delta > 45 && delta < 315)
+		return FALSE;
+	return TRUE;
+};
+
+
+//=============================================================================
+
+.float() th_checkattack;
+
+
+
+/*
+=============
+ai_run
+
+The monster has an enemy it is trying to kill
+=============
+*/
+void(float dist) ai_run =
+{
+	local float ofs;
+	if (self.health < 1)
+		return;
+	movedist = dist;
+	// see if the enemy is dead
+	if (self.enemy.health < 1 || self.enemy.takedamage == DAMAGE_NO)
+	{
+		self.enemy = world;
+		// FIXME: look all around for other targets
+		if (self.oldenemy.health >= 1 && self.oldenemy.takedamage)
+		{
+			self.enemy = self.oldenemy;
+			self.oldenemy = world;
+			HuntTarget ();
+		}
+		else
+		{
+			if (self.movetarget)
+				self.th_walk ();
+			else
+				self.th_stand ();
+			return;
+		}
+	}
+
+	// wake up other monsters
+	self.show_hostile = time + 1;
+
+	// check knowledge of enemy
+	enemy_range = range(self.enemy);
+
+	self.ideal_yaw = vectoyaw(self.enemy.origin - self.origin);
+	ChangeYaw ();
+
+	if (self.attack_state == AS_MELEE)
+	{
+		//dprint ("ai_run_melee\n");
+		//Turn and close until within an angle to launch a melee attack
+		if (FacingIdeal())
+		{
+			self.th_melee ();
+			self.attack_state = AS_STRAIGHT;
+		}
+		return;
+	}
+	else if (self.attack_state == AS_MISSILE)
+	{
+		//dprint ("ai_run_missile\n");
+		//Turn in place until within an angle to launch a missile attack
+		if (FacingIdeal())
+		if (self.th_missile ())
+			self.attack_state = AS_STRAIGHT;
+		return;
+	}
+
+	if (self.th_checkattack())
+		return;					// beginning an attack
+
+	if (visible(self.enemy))
+		self.search_time = time + 5;
+	else if (coop)
+	{
+		// look for other coop players
+		if (self.search_time < time)
+			self.findtarget = TRUE;
+	}
+
+	if (self.attack_state == AS_SLIDING)
+	{
+		//dprint ("ai_run_slide\n");
+		//Strafe sideways, but stay at aproximately the same range
+		if (self.lefty)
+			ofs = 90;
+		else
+			ofs = -90;
+
+		if (walkmove (self.ideal_yaw + ofs, movedist))
+			return;
+
+		self.lefty = !self.lefty;
+
+		walkmove (self.ideal_yaw - ofs, movedist);
+	}
+
+	// head straight in
+	movetogoal (dist);		// done in C code...
+};
+

Added: trunk/data/qcsrc/server/monsters/defs.qc
===================================================================
--- trunk/data/qcsrc/server/monsters/defs.qc	                        (rev 0)
+++ trunk/data/qcsrc/server/monsters/defs.qc	2009-01-14 09:07:30 UTC (rev 5515)
@@ -0,0 +1,53 @@
+.entity movetarget;
+.float pausetime;
+
+.void()         th_stand;
+.void()         th_walk;
+.void()         th_run;
+.float()        th_missile; // LordHavoc: changed from void() to float(), returns true if attacking
+.void()         th_melee;
+.void(entity attacker, float damage, float damgtype, string dethtype)           th_pain;
+.void()         th_die;
+.entity         oldenemy;               // mad at this player before taking damage
+entity  newmis;                 // launch_spike sets this after spawning it
+
+// range values
+float   RANGE_MELEE                             = 0;
+float   RANGE_NEAR                              = 1;
+float   RANGE_MID                               = 2;
+float   RANGE_FAR                               = 3;
+
+float DMG_KNIGHT_MELEE_BASE           =    0;
+float DMG_KNIGHT_MELEE_RANDOM1        =    3;
+float DMG_KNIGHT_MELEE_RANDOM2        =    3;
+float DMG_KNIGHT_MELEE_RANDOM3        =    3;
+
+.float          show_hostile; 
+	// set to time+0.2 whenever a client fires a
+	// weapon or takes damage.  Used to alert
+	// monsters that otherwise would let the player go
+
+float movedist;
+.float lefty;
+.float search_time;
+.float attack_state;
+
+float   AS_STRAIGHT             = 1;
+float   AS_SLIDING              = 2;
+float   AS_MELEE                = 3;
+float   AS_MISSILE              = 4;
+
+float SKILL4_MINALPHA         = 0.4;
+
+float monsterwander;
+/*
+        monsterwander = cvar("monsterwander");
+        // monsterwander is always on in skill 5
+        if (skill >= 5)
+                monsterwander = TRUE;
+*/
+
+.float candrown;
+
+.void(vector org, float bodydamage, float armordamage, vector vel, float damgtype) bleedfunc;
+void(vector org, float bodydamage, float armordamage, vector vel, float damgtype) genericbleedfunc;

Added: trunk/data/qcsrc/server/monsters/fight.qc
===================================================================
--- trunk/data/qcsrc/server/monsters/fight.qc	                        (rev 0)
+++ trunk/data/qcsrc/server/monsters/fight.qc	2009-01-14 09:07:30 UTC (rev 5515)
@@ -0,0 +1,252 @@
+
+/*
+
+A monster is in fight mode if it thinks it can effectively attack its
+enemy.
+
+When it decides it can't attack, it goes into hunt mode.
+
+*/
+
+void SUB_AttackFinished (float normal)
+{
+	self.cnt = 0;           // refire count for nightmare
+	if (skill < 3)
+		ATTACK_FINISHED(self) = time + normal;
+}
+
+float CanDamage(entity targ, entity inflictor)
+{
+	if (targ.movetype == MOVETYPE_PUSH)
+	{
+		traceline(inflictor.origin, 0.5 * (targ.absmin + targ.absmax), TRUE, self);
+		if (trace_fraction == 1)
+			return TRUE;
+		if (trace_ent == targ)
+			return TRUE;
+		return FALSE;
+	}
+
+	traceline(inflictor.origin, targ.origin, TRUE, self);
+	if (trace_fraction == 1)
+		return TRUE;
+	traceline(inflictor.origin, targ.origin + '15 15 0', TRUE, self);
+	if (trace_fraction == 1)
+		return TRUE;
+	traceline(inflictor.origin, targ.origin + '-15 -15 0', TRUE, self);
+	if (trace_fraction == 1)
+		return TRUE;
+	traceline(inflictor.origin, targ.origin + '-15 15 0', TRUE, self);
+	if (trace_fraction == 1)
+		return TRUE;
+	traceline(inflictor.origin, targ.origin + '15 -15 0', TRUE, self);
+	if (trace_fraction == 1)
+		return TRUE;
+
+	return FALSE;
+}
+
+float(float v) anglemod;
+
+void(vector dest) ChooseTurn;
+
+void() ai_face;
+
+
+float enemy_range;
+
+
+//=============================================================================
+
+/*
+===========
+GenericCheckAttack
+
+The player is in view, so decide to move or launch an attack
+Returns FALSE if movement should continue
+============
+*/
+float() GenericCheckAttack =
+{
+	local vector spot1, spot2;
+	local entity targ;
+	local float chance;
+
+	if (self.health < 1)
+		return FALSE;
+	targ = self.enemy;
+
+	if (vlen(targ.origin - self.origin) > 5000) // long traces are slow
+		return FALSE;
+
+// see if any entities are in the way of the shot
+	spot1 = self.origin + self.view_ofs;
+	spot2 = targ.origin + targ.view_ofs;
+
+	traceline (spot1, spot2, FALSE, self);
+
+	if (trace_ent != targ)
+		return FALSE; // don't have a clear shot
+
+	if (trace_inopen && trace_inwater)
+		return FALSE; // sight line crossed contents
+
+	if (enemy_range == RANGE_MELEE)
+	{	// melee attack
+		if (self.th_melee)
+		{
+			self.th_melee ();
+			return TRUE;
+		}
+	}
+
+// missile attack
+	if (time < ATTACK_FINISHED(self))
+		return FALSE;
+
+	if (!self.th_missile)
+		return FALSE;
+
+	if (enemy_range == RANGE_FAR)
+		return FALSE;
+
+	if (enemy_range == RANGE_MELEE)
+	{
+		chance = 0.9;
+		ATTACK_FINISHED(self) = 0;
+	}
+	else if (enemy_range == RANGE_NEAR)
+	{
+		if (self.th_melee)
+			chance = 0.2;
+		else
+			chance = 0.4;
+	}
+	else if (enemy_range == RANGE_MID)
+	{
+		if (self.th_melee)
+			chance = 0.05;
+		else
+			chance = 0.1;
+	}
+	else
+		chance = 0;
+
+	if (random () < chance)
+	if (self.th_missile ())
+	{
+		SUB_AttackFinished (2*random());
+		return TRUE;
+	}
+
+	return FALSE;
+};
+
+
+/*
+=============
+ai_face
+
+Stay facing the enemy
+=============
+*/
+void() ai_face =
+{
+	self.ideal_yaw = vectoyaw(self.enemy.origin - self.origin);
+	ChangeYaw ();
+};
+
+/*
+=============
+ai_charge
+
+The monster is in a melee attack, so get as close as possible to .enemy
+=============
+*/
+float (entity targ) visible;
+float(entity targ) infront;
+float(entity targ) range;
+
+void(float d) ai_charge =
+{
+	if (self.health < 1)
+		return;
+	ai_face ();
+	movetogoal (d);		// done in C code...
+};
+
+void() ai_charge_side =
+{
+	if (self.health < 1)
+		return;
+	local vector dtemp;
+	local float heading;
+
+// aim to the left of the enemy for a flyby
+
+	self.ideal_yaw = vectoyaw(self.enemy.origin - self.origin);
+	ChangeYaw ();
+
+	makevectors (self.angles);
+	dtemp = self.enemy.origin - 30*v_right;
+	heading = vectoyaw(dtemp - self.origin);
+
+	walkmove(heading, 20);
+};
+
+
+/*
+=============
+ai_melee
+
+=============
+*/
+void() ai_melee =
+{
+	local vector delta;
+	local float ldmg;
+
+	if (self.health < 1)
+		return;
+	if (!self.enemy)
+		return;		// removed before stroke
+
+	delta = self.enemy.origin - self.origin;
+
+	if (vlen(delta) > 60)
+		return;
+
+	ldmg = DMG_KNIGHT_MELEE_BASE + DMG_KNIGHT_MELEE_RANDOM1 * random();
+	ldmg = ldmg + DMG_KNIGHT_MELEE_RANDOM2 * random();
+	ldmg = ldmg + DMG_KNIGHT_MELEE_RANDOM3 * random();
+	traceline(self.origin, self.enemy.origin, FALSE, self);
+
+	Damage (self.enemy, self, self, ldmg, self.projectiledeathtype, trace_endpos, '0 0 0'); // TODO add force to monster melee attacks?
+};
+
+
+void() ai_melee_side =
+{
+	local vector delta;
+	local float ldmg;
+
+	if (self.health < 1)
+		return;
+	if (!self.enemy)
+		return;		// removed before stroke
+
+	ai_charge_side();
+
+	delta = self.enemy.origin - self.origin;
+
+	if (vlen(delta) > 60)
+		return;
+	if (!CanDamage (self.enemy, self))
+		return;
+	ldmg = DMG_KNIGHT_MELEE_BASE + DMG_KNIGHT_MELEE_RANDOM1 * random();
+	ldmg = ldmg + DMG_KNIGHT_MELEE_RANDOM2 * random();
+	ldmg = ldmg + DMG_KNIGHT_MELEE_RANDOM3 * random();
+	traceline(self.origin, self.enemy.origin, FALSE, self);
+	Damage (self.enemy, self, self, ldmg, self.projectiledeathtype, trace_endpos, '0 0 0');
+};
+

Added: trunk/data/qcsrc/server/monsters/m_monsters.qc
===================================================================
--- trunk/data/qcsrc/server/monsters/m_monsters.qc	                        (rev 0)
+++ trunk/data/qcsrc/server/monsters/m_monsters.qc	2009-01-14 09:07:30 UTC (rev 5515)
@@ -0,0 +1,467 @@
+/* ALL MONSTERS SHOULD BE 1 0 0 IN COLOR */
+
+// name =[framenum,	nexttime, nextthink] {code}
+// expands to:
+// name ()
+// {
+//		self.frame=framenum;
+//		self.nextthink = time + nexttime;
+//		self.think = nextthink
+//		<code>
+// };
+
+.float ismonster;
+
+.float modelindex2;
+
+/*
+================
+monster_use
+
+Using a monster makes it angry at the current activator
+LordHavoc: using a monster with the spawnflag 'Appear' makes it appear
+================
+*/
+void() monster_use =
+{
+	if (self.enemy)
+		return;
+	if (self.health < 1)
+		return;
+	if (self.mdl)
+	if (self.spawnflags & MONSTER_APPEAR)
+	{
+		self.nextthink = time + 0.1;
+		self.spawnflags = self.spawnflags - MONSTER_APPEAR;
+		self.solid = SOLID_SLIDEBOX;
+		self.takedamage = DAMAGE_AIM;
+		//self.movetype = MOVETYPE_STEP;
+		self.model = self.mdl;
+		self.mdl = "";
+		self.modelindex = self.modelindex2;
+		self.modelindex2 = 0;
+		//setorigin(self, self.origin + '0 0 1');
+		spawn_tdeath(self.origin, self, self.origin);
+		return;
+	}
+
+#if 0
+	if (activator.items & IT_INVISIBILITY)
+		return;
+#endif
+	if (activator.flags & FL_NOTARGET)
+		return;
+	if (activator.classname != "player")
+		return;
+
+	// delay reaction so if the monster is teleported, its sound is still heard
+	self.enemy = activator;
+	self.nextthink = time + 0.1;
+	self.think = FoundTarget;
+};
+
+void() monster_appearsetup =
+{
+	if ((self.spawnflags & MONSTER_APPEAR) == 0)
+		return;
+	self.mdl = self.model;
+	self.modelindex2 = self.modelindex;
+	self.modelindex = 0;
+	self.solid = SOLID_NOT;
+	self.takedamage = DAMAGE_NO;
+	//self.movetype = MOVETYPE_NONE;
+	self.nextthink = -1;
+	self.model = "";
+};
+
+/*
+================
+monster_setalpha
+
+Sets relative alpha of monster in skill 4 mode.
+================
+*/
+void(float a) monster_setalpha =
+{
+	if (skill < 4 || self.classname == "monster_hellfish")
+	{
+		self.alpha = 1.0;
+		return;
+	}
+
+	if (skill >= 5)
+	{
+		// randomly forget enemy, this makes monsters randomly return to their normal ghostlike state
+		if (a == 0)
+		if (self.enemy)
+		if (random() < 0.1)
+			self.enemy = world;
+		// randomly blink (playing the same alarming sound as if attacking)
+		if (self.enemy == world)
+		{
+			a = 0;
+			if (time >= 0.3) // don't blink during the init process because it might become permanent
+			if (random() < 0.005)
+			{
+				// blink for an instant, this causes the appear sound, alarming the player as if under attack
+				sound(self, CHAN_AUTO, "wizard/wsight.wav", 1, ATTN_NORM);
+				a = 1;
+			}
+		}
+		// if ghosted, become non-solid and immune to damage
+		if (a <= 0 || self.enemy == world)
+		{
+			self.solid = SOLID_NOT;
+			self.takedamage = DAMAGE_NO;
+		}
+		else
+		{
+			// if unghosting, make sure we have an enemy, otherwise stay ghosted (even if blinking) so we can't be shot while blinking
+			if (self.solid != SOLID_SLIDEBOX)
+				sound(self, CHAN_AUTO, "wizard/wsight.wav", 1, ATTN_NORM);
+			self.solid = SOLID_SLIDEBOX;
+			self.takedamage = DAMAGE_AIM;
+		}
+	}
+	self.alpha = SKILL4_MINALPHA + (1 - SKILL4_MINALPHA) * bound(0, a, 1);
+};
+
+/*
+================
+monster_death_use
+
+When a mosnter dies, it fires all of its targets with the current
+enemy as activator.
+================
+*/
+void() monster_death_use =
+{
+// fall to ground
+	if (self.flags & FL_FLY)
+		self.flags = self.flags - FL_FLY;
+	if (self.flags & FL_SWIM)
+		self.flags = self.flags - FL_SWIM;
+
+	if (!self.target)
+		return;
+
+	activator = self.enemy;
+	SUB_UseTargets ();
+};
+
+
+void() monsterinwall =
+{
+	local entity e;
+	if (!cvar("developer"))
+		return;
+	// this is handy for level designers,
+	// puts a spikey ball where the error is...
+	e = spawn();
+	setorigin(e, self.origin);
+	setmodel (e, "progs/star.mdl");
+	e.movetype = MOVETYPE_NONE;
+	e.solid = SOLID_NOT;
+	e.think = SUB_Null;
+	e.nextthink = -1;
+};
+
+//============================================================================
+
+void() walkmonster_start_go =
+{
+	self.origin_z = self.origin_z + 1; // raise off floor a bit
+
+	tracebox(self.origin, self.mins, self.maxs, self.origin, TRUE, self);
+	if (trace_startsolid)
+	{
+		dprint("walkmonster in wall at: ");
+		dprint(vtos(self.origin));
+		dprint("\n");
+		monsterinwall();
+		droptofloor();
+	}
+	else
+	{
+		droptofloor();
+		if (!walkmove(0,0))
+		{
+			dprint("walkmonster in wall at: ");
+			dprint(vtos(self.origin));
+			dprint("\n");
+			monsterinwall();
+		}
+	}
+
+	//self.cantrigger = TRUE;
+
+	self.takedamage = DAMAGE_AIM;
+
+	self.ideal_yaw = self.angles * '0 1 0';
+	if (!self.yaw_speed)
+		self.yaw_speed = 20;
+	self.view_ofs = '0 0 25';
+	self.use = monster_use;
+
+	self.flags = self.flags | FL_MONSTER;
+
+	if (monsterwander)
+		self.spawnflags = self.spawnflags | MONSTER_WANDER;
+
+	if (self.target)
+	{
+		self.goalentity = self.movetarget = find(world, targetname, self.target);
+		self.ideal_yaw = vectoyaw(self.goalentity.origin - self.origin);
+		if (!self.movetarget)
+		{
+			dprint("Monster can't find target at ");
+			dprint(vtos(self.origin));
+			dprint("\n");
+		}
+		// this used to be an objerror
+		if (self.movetarget.classname == "path_corner")
+			self.th_walk ();
+		else
+		{
+			if ((self.spawnflags & MONSTER_WANDER) && (!self.monsterawaitingteleport) && (self.spawnflags & 3) == 0 && (world.model != "maps/e1m7.bsp"))
+			{
+				monster_spawnwanderpath();
+				self.ideal_yaw = vectoyaw(self.goalentity.origin - self.origin);
+				self.th_walk ();
+			}
+			else
+			{
+				self.pausetime = 99999999;
+				self.th_stand ();
+			}
+		}
+	}
+	else
+	{
+		if ((self.spawnflags & MONSTER_WANDER) && (!self.monsterawaitingteleport) && (self.spawnflags & 3) == 0 && (world.model != "maps/e1m7.bsp"))
+		{
+			monster_spawnwanderpath();
+			self.ideal_yaw = vectoyaw(self.goalentity.origin - self.origin);
+			self.th_walk ();
+		}
+		else
+		{
+			self.pausetime = 99999999;
+			self.th_stand ();
+		}
+	}
+
+// spread think times so they don't all happen at same time
+	self.nextthink = self.nextthink + random()*0.5 + 0.1;
+	self.iscreature = TRUE;
+
+	force_retouch = 2; // mainly to detect teleports
+
+	monster_appearsetup();
+};
+
+
+void() walkmonster_start =
+{
+	self.candrown = 1; // this is turned off by some monsters like zombies
+	// delay drop to floor to make sure all doors have been spawned
+	// spread think times so they don't all happen at same time
+	self.nextthink = time + random()*0.5 + 0.3;
+	self.think = walkmonster_start_go;
+	total_monsters = total_monsters + 1;
+	self.bot_attack = TRUE;
+	self.frags = 2; // actually just used to get havocbots to attack it...
+	self.bleedfunc = genericbleedfunc;
+	self.ismonster = TRUE;
+
+	monster_setalpha (0);
+};
+
+
+
+void() flymonster_start_go =
+{
+	self.takedamage = DAMAGE_AIM;
+
+	self.ideal_yaw = self.angles * '0 1 0';
+	if (!self.yaw_speed)
+		self.yaw_speed = 10;
+	self.view_ofs = '0 0 25';
+	self.use = monster_use;
+
+	self.flags = self.flags | FL_FLY;
+	self.flags = self.flags | FL_MONSTER;
+
+	if (!walkmove(0,0))
+	{
+		dprint("flymonster in wall at: ");
+		dprint(vtos(self.origin));
+		dprint("\n");
+		monsterinwall();
+	}
+
+	//self.cantrigger = TRUE;
+
+	if (monsterwander)
+		self.spawnflags = self.spawnflags | MONSTER_WANDER;
+
+	if (self.target)
+	{
+		self.goalentity = self.movetarget = find(world, targetname, self.target);
+		if (!self.movetarget)
+		{
+			dprint("Monster can't find target at ");
+			dprint(vtos(self.origin));
+			dprint("\n");
+		}
+		// this used to be an objerror
+		if (self.movetarget.classname == "path_corner")
+			self.th_walk ();
+		else
+		{
+			if ((self.spawnflags & MONSTER_WANDER) && (!self.monsterawaitingteleport) && (self.spawnflags & 3) == 0 && (world.model != "maps/e1m7.bsp"))
+			{
+				monster_spawnwanderpath();
+				self.ideal_yaw = vectoyaw(self.goalentity.origin - self.origin);
+				self.th_walk ();
+			}
+			else
+			{
+				self.pausetime = 99999999;
+				self.th_stand ();
+			}
+		}
+	}
+	else
+	{
+		if ((self.spawnflags & MONSTER_WANDER) && (!self.monsterawaitingteleport) && (self.spawnflags & 3) == 0 && (world.model != "maps/e1m7.bsp"))
+		{
+			monster_spawnwanderpath();
+			self.ideal_yaw = vectoyaw(self.goalentity.origin - self.origin);
+			self.th_walk ();
+		}
+		else
+		{
+			self.pausetime = 99999999;
+			self.th_stand ();
+		}
+	}
+	self.iscreature = TRUE;
+
+	force_retouch = 2; // mainly to detect teleports
+
+	monster_appearsetup();
+};
+
+void() flymonster_start =
+{
+	self.candrown = 1;
+	// spread think times so they don't all happen at same time
+	self.nextthink = time + random()*0.5 + 0.1;
+	self.think = flymonster_start_go;
+	total_monsters = total_monsters + 1;
+	self.bot_attack = TRUE;
+	self.frags = 2; // actually just used to get havocbots to attack it...
+	self.bleedfunc = genericbleedfunc;
+	self.ismonster = TRUE;
+
+	monster_setalpha (0);
+};
+
+
+void() swimmonster_start_go =
+{
+	if (deathmatch)
+	{
+		remove(self);
+		return;
+	}
+
+	//self.cantrigger = TRUE;
+
+	self.takedamage = DAMAGE_AIM;
+
+	self.ideal_yaw = self.angles * '0 1 0';
+	if (!self.yaw_speed)
+		self.yaw_speed = 10;
+	self.view_ofs = '0 0 10';
+	self.use = monster_use;
+
+	self.flags = self.flags | FL_SWIM;
+	self.flags = self.flags | FL_MONSTER;
+
+	if (monsterwander)
+		self.spawnflags = self.spawnflags | MONSTER_WANDER;
+
+	if (self.target)
+	{
+		self.goalentity = self.movetarget = find(world, targetname, self.target);
+		if (!self.movetarget)
+		{
+			dprint("Monster can't find target at ");
+			dprint(vtos(self.origin));
+			dprint("\n");
+		}
+		// this used to be an objerror
+		if (self.movetarget.classname == "path_corner")
+			self.th_walk ();
+		else
+		{
+			if ((self.spawnflags & MONSTER_WANDER) && (!self.monsterawaitingteleport) && (self.spawnflags & 3) == 0 && (world.model != "maps/e1m7.bsp"))
+			{
+				monster_spawnwanderpath();
+				self.ideal_yaw = vectoyaw(self.goalentity.origin - self.origin);
+				self.th_walk ();
+			}
+			else
+			{
+				self.pausetime = 99999999;
+				self.th_stand ();
+			}
+		}
+	}
+	else
+	{
+		if ((self.spawnflags & MONSTER_WANDER) && (!self.monsterawaitingteleport) && (self.spawnflags & 3) == 0 && (world.model != "maps/e1m7.bsp"))
+		{
+			monster_spawnwanderpath();
+			self.ideal_yaw = vectoyaw(self.goalentity.origin - self.origin);
+			self.th_walk ();
+		}
+		else
+		{
+			self.pausetime = 99999999;
+			self.th_stand ();
+		}
+	}
+	self.iscreature = TRUE;
+
+	force_retouch = 2; // mainly to detect teleports
+
+	monster_appearsetup();
+};
+
+void() swimmonster_start =
+{
+	// spread think times so they don't all happen at same time
+	self.candrown = 0;
+	self.nextthink = time + random()*0.5 + 0.1;
+	self.think = swimmonster_start_go;
+	total_monsters = total_monsters + 1;
+	self.bot_attack = TRUE;
+	self.frags = 2; // actually just used to get havocbots to attack it...
+	self.bleedfunc = genericbleedfunc;
+	self.ismonster = TRUE;
+
+	monster_setalpha(0);
+};
+
+void(vector org, float bodydamage, float armordamage, vector force, float damgtype) genericbleedfunc =
+{
+        local vector v;
+        v = '0 0 0' - force * 0.05;
+        if (armordamage > 0)
+                te_spark(org, v, armordamage * 3);
+        if (bodydamage > 0)
+                te_blood(org, v, bodydamage);
+}

Added: trunk/data/qcsrc/server/monsters/mode_management.qc
===================================================================
--- trunk/data/qcsrc/server/monsters/mode_management.qc	                        (rev 0)
+++ trunk/data/qcsrc/server/monsters/mode_management.qc	2009-01-14 09:07:30 UTC (rev 5515)
@@ -0,0 +1,526 @@
+
+string(float c) colorname =
+{
+	// yikes, the quake color set is HARD to describe
+	// many are easy, but, uh, 2 browns???
+	// 2 purples???
+	// that 'pink' is hard to classify
+	// I think 'biege' is a fairly good name for color 10
+	// oh well, gotta do all the color names...
+	if (c ==  0) return "white";
+	else if (c ==  1) return "brown";
+	else if (c ==  2) return "lightblue";
+	else if (c ==  3) return "green";
+	else if (c ==  4) return "red";
+	else if (c ==  5) return "lighterbrown";
+	else if (c ==  6) return "orange";
+	else if (c ==  7) return "pink";
+	else if (c ==  8) return "purple";
+	else if (c ==  9) return "redishpurple";
+	else if (c == 10) return "biege";
+	else if (c == 11) return "aqua";
+	else if (c == 12) return "yellow";
+	else if (c == 13) return "blue";
+	else if (c == 14) return "flamingorange";
+	else if (c == 15) return "psychadelic";
+	else              return "INVALID COLOR";
+};
+
+float mode_shirtmustmatchpants;
+float mode_numteams;
+float mode_allowedteams[17];
+float mode_teamcount[17];
+float mode_teamscore[17];
+
+void() mode_initallowedteams =
+{
+	local float c;
+	c = 0;
+	while(c < 17)
+	{
+		mode_allowedteams[c] = FALSE;
+		c = c + 1;
+	}
+	mode_allowedteams[5] = TRUE; // red
+	mode_allowedteams[14] = TRUE; // blue
+	if (deathmatch == DM_ELIM
+	 || deathmatch == DM_ONEVSALL
+	 || deathmatch == DM_CTF_2TEAM
+	 || deathmatch == DM_DOMINATION
+	 || deathmatch == DM_SUPERDOMINATION)
+		mode_numteams = 2;
+	else if (deathmatch == DM_CTF_3TEAM)
+	{
+		mode_numteams = 3;
+		mode_allowedteams[13] = TRUE; // yellow
+	}
+	else
+	{
+		mode_numteams = 16;
+		c = 1;
+		while(c < 17)
+		{
+			mode_allowedteams[c] = TRUE;
+			c = c + 1;
+		}
+	}
+};
+
+float(float t) validteam =
+{
+	return mode_allowedteams[t];
+};
+
+float() weakestteam =
+{
+	local float bestteam;
+	local float bestteamcount;
+	local float headcount;
+	local float c;
+	bestteam = -1;
+	bestteamcount = 0;
+	c = 1;
+	while (c < 17)
+	{
+		if (mode_allowedteams[c])
+		{
+			headcount = mode_teamcount[c];
+			if (bestteamcount > headcount || bestteam == -1)
+			{
+				bestteamcount = headcount;
+				bestteam = c;
+			}
+		}
+		c = c + 1;
+	}
+	return bestteam;
+};
+
+void() updateteams =
+{
+	local entity head;
+	local float c;
+	c = 1;
+	while (c < 17)
+	{
+		mode_teamcount[c] = 0;
+		mode_teamscore[c] = 0;
+		c = c + 1;
+	}
+	c = 0;
+	head = nextent(world);
+	while (c < maxclients)
+	{
+		mode_teamcount[head.team] = mode_teamcount[head.team] + 1;
+		mode_teamscore[head.team] = mode_teamscore[head.team] + head.frags;
+		c = c + 1;
+		head = nextent(head);
+	}
+};
+
+float(float p) checkteamcolor =
+{
+	if (!validteam(p + 1))
+		p = weakestteam() - 1;
+	return p;
+};
+
+void(float c) SV_ChangeTeam =
+{
+	local float pants, shirt, old;
+	old = self.clientcolors & 15;
+	if (c >= 0)
+	{
+		pants = c & 15;
+		shirt = (c / 16) & 15;
+	}
+	else
+	{
+		pants = -1;
+		shirt = -1;
+	}
+	pants = checkteamcolor(pants);
+	if (mode_shirtmustmatchpants || shirt < 0)
+		shirt = pants;
+	setcolor(self, pants + shirt * 16);
+	if (pants != old && old >= 0 && teamplay && deathmatch)
+	{
+		T_Damage(self, self, self, 0, 0, " changed teams", DT_TELEFRAG, self.origin, '0 0 0', Obituary_Generic);
+		self.frags = 0;
+		PutClientInServer ();
+	}
+};
+
+void() checkinvalidteam =
+{
+	// call SV_ChangeTeam to trigger the weakestteam change
+	if (!validteam(self.team))
+		SV_ChangeTeam(self.team - 1);
+};
+
+
+string dmmessage;
+
+void(string m) setdm =
+{
+	dmmessage = m;
+	if (cvar_string("deathmatch") != m)
+		cvar_set("deathmatch", m);
+}
+
+void(string m) setteamplay =
+{
+	dmmessage = m;
+	if (cvar_string("teamplay") != m)
+		cvar_set("teamplay", m);
+}
+
+void() mode_updatecvars =
+{
+	local float dm, tp;
+	dm = cvar("deathmatch");
+	tp = cvar("teamplay");
+	// now set deathmatch cvar
+	if (dm ==  0) setdm("0?Dark Places - Coop");
+	else if (dm ==  1) setdm("1?Dark Places - Deathmatch");
+	else if (dm ==  2) setdm("2?Dark Places - Deathmatch 2 (can only pickup gun once)");
+	else if (dm ==  3) setdm("3?Dark Places - Deathmatch 3 (quick ammo respawn)");
+	else if (dm ==  5) setdm("5?Dark Places - Frag Fest (spawn with full pack)");
+//	else if (dm ==  6) setdm("6?Dark Places - Random Weapons (spawn with 2 random weapons)"); // removed
+	else if (dm ==  7) setdm("7?Dark Places - Monsters");
+//	else if (dm ==  8) setdm("8?Dark Places - Elimination");
+//	else if (dm ==  9) setdm("9?Dark Places - Kill The Leader Mode");
+	else if (dm == 10) setdm("10?Dark Places - Capture The Flag - 2 Team");
+	else if (dm == 11) setdm("11?Dark Places - Capture The Flag - 3 Team");
+	else if (dm == 12) setdm("12?Dark Places - Domination");
+	else if (dm == 13) setdm("13?Dark Places - Monster Capture The Flag - 2 Team");
+	else if (dm == 14) setdm("14?Dark Places - Super Domination");
+	else if (dm == 30) setdm("30?Dark Places - Role Playing Game");
+	else                               setdm("1?Dark Places - Deathmatch");
+
+	// now set teamplay cvar
+	if (dm == 0) setteamplay("4?Dark Places - Coop (Can't hurt other players)");
+	//else if (dm == 8) setteamplay("3?Dark Places - Elimination");
+	//else if (dm == 9) setteamplay("3?Dark Places - Kill The Leader");
+	else if (dm == 10) setteamplay("3?Dark Places - Capture The Flag - 2 Team");
+	else if (dm == 11) setteamplay("3?Dark Places - Capture The Flag - 3 Team");
+	else if (dm == 12) setteamplay("3?Dark Places - Domination");
+	else if (dm == 13) setteamplay("3?Dark Places - Monster Capture The Flag - 2 Team");
+	else
+	{
+		if (tp == 0) setteamplay("0?Dark Places - No Teamplay");
+		else if (tp == 1) setteamplay("1?Dark Places - No team damage");
+		else if (tp == 2) setteamplay("2?Dark Places - Can hurt anyone");
+		else if (tp == 3) setteamplay("3?Dark Places - No team damage, but can hurt self");
+		else              setteamplay("0?Dark Places - No Teamplay");
+	}
+};
+
+float nextcvarupdate;
+void() deathmatch7update;
+void() modeupdate =
+{
+	if (time > nextcvarupdate)
+	{
+		nextcvarupdate = time + 1;
+		mode_updatecvars();
+	}
+	deathmatch7update();
+};
+
+// true if items should respawn
+float itemrespawn;
+// when the next monster spawning check will occur in deathmatch 7 mode
+float spawnchecktime;
+
+void() precachemonsters;
+void() superdomination_precache;
+void() modesetup =
+{
+	mode_shirtmustmatchpants = deathmatch >= DM_TEAM_MODS_START && deathmatch < DM_TEAM_MODS_END;
+	mode_initallowedteams();
+
+	itemrespawn = cvar("deathmatch") + cvar("coop");
+
+	// don't spawn any monsters until 15 seconds
+	spawnchecktime = 15;
+	if (deathmatch == 7 || cvar("spawnmonsters") >= 1)
+		precachemonsters();
+
+	superdomination_precache();
+};
+
+float monsterspawn;
+void() spawnmonster_think =
+{
+	//local float c;
+	local void() sfunc;
+	self.nextthink = time;
+	if (time > self.cnt)
+	{
+		remove(self);
+		return;
+	}
+	if (vlen(self.velocity) > 5)
+		return; // try again later
+
+	//if (!(self.flags & FL_FLY))
+	//	droptofloor();
+	// don't spawn if something is in the way
+	/*
+	// walk around a lot
+	if (walkmove(0,0))
+	{
+		if (self.lefty > 0)
+		{
+			c = 100;
+			self.lefty = self.lefty - 1;
+			self.angles = '0 0 0';
+			while(c > 0)
+			{
+				c = c - 1;
+				if (!walkmove(self.angles_y, 16))
+					self.angles_y = random() * 360;
+			}
+			self.angles = '0 0 0';
+			return;
+		}
+	}
+	*/
+	// don't spawn if something is in the way
+	if (!walkmove(0,0))
+	{
+		self.lefty = 10;
+		setorigin(self, self.dest);
+		self.flags = self.flags - (self.flags & FL_ONGROUND);
+		self.velocity = randomvec() * 700 + '0 0 1000';
+		return;
+	}
+	newmis = findchain(classname, "player");
+	while (newmis)
+	{
+		if (vlen(newmis.origin - self.origin) < 300)
+			return;
+		newmis = newmis.chain;
+	}
+
+	if (self.netname == "monster_fish")
+	{
+		if (pointcontents(self.origin) != CONTENT_WATER)
+		{
+			remove(self);
+			return;
+		}
+	}
+
+	// spawn in
+	self.movetype = MOVETYPE_NONE;
+	self.solid = SOLID_NOT;
+	self.velocity = '0 0 0';
+	self.flags = 0;
+	self.model = "";
+	self.modelindex = 0;
+	setorigin(self, self.origin);
+	self.angles = '0 360 0' * random();
+	self.classname = self.netname;
+	self.netname = "";
+	self.cnt = 0;
+	self.think = SUB_Remove;
+	sfunc = self.th_run;
+	self.th_run = SUB_Null;
+	te_teleport(self.origin);
+	monsterspawn = TRUE;
+	sfunc();
+	monsterspawn = FALSE;
+};
+
+void(vector org, float c1, float c2, string cname, void() spawnfunc, vector m1, vector m2) spawnmonster =
+{
+	local float c;
+	c = (c2 - c1) * random() + c1;
+	c = rint(c);
+	while (c > 0)
+	{
+		c = c - 1;
+
+		newmis = spawn();
+		newmis.cnt = time + 10;
+		if (cname == "monster_wizard")
+			newmis.cnt = time + 2;
+		newmis.lefty = 10;
+		newmis.dest = org;
+		newmis.classname = "spawningmonster";
+		newmis.netname = cname;
+		newmis.solid = SOLID_TRIGGER;
+		newmis.movetype = MOVETYPE_TOSS;
+		newmis.flags = FL_MONSTER; // make this count as a monster even though it hasn't spawned in yet
+		newmis.velocity = randomvec() * 700 + '0 0 1000';
+		newmis.th_run = spawnfunc;
+		newmis.think = spawnmonster_think;
+		newmis.nextthink = time + random() * 0.5 + 0.3;
+		setorigin(newmis, org);
+		setmodel(newmis, "progs/s_explod.spr");
+		setsize(newmis, m1, m2);
+	}
+};
+
+void() monster_army;
+void() monster_demon1;
+void() monster_dog;
+void() monster_enforcer;
+void() monster_hell_knight;
+void() monster_knight;
+void() monster_ogre;
+void() monster_shalrath;
+void() monster_shambler;
+void() monster_tarbaby;
+void() monster_wizard;
+void() monster_zombie;
+void() monster_fish;
+void() monster_hellfish;
+
+void() spawnmonsters =
+{
+	local float r;
+	local vector org;
+	local entity head, e;
+	head = findchain(classname, "info_player_deathmatch");
+	if (head == world)
+	{
+		head = findchain(classname, "info_player_coop");
+		if (head == world)
+		{
+			head = findchain(classname, "info_player_start");
+			if (head == world)
+				return;
+		}
+	}
+
+	// count the spawn points
+	r = 0;
+	e = head;
+	while (e)
+	{
+		r = r + 1;
+		e = e.chain;
+	}
+
+	// pick a random one
+	r = random() * r;
+	e = head;
+	while (r > 0)
+	{
+		r = r - 1;
+		org = e.origin;
+		e = e.chain;
+	}
+
+	// pick a type of monster
+	if (cvar("registered"))
+	{
+		r = floor(random() * 13);
+		if (r > 12)
+			r = 12;
+	}
+	else
+	{
+		r = floor(random() * 8);
+		if (r > 7)
+			r = 7;
+	}
+	     if (r ==  0) spawnmonster(org,  5, 10, "monster_army"       , monster_army       , '-16 -16 -24', '16 16 32');
+	else if (r ==  1) spawnmonster(org,  3,  6, "monster_demon1"     , monster_demon1     , '-32 -32 -24', '32 32 64');
+	else if (r ==  2) spawnmonster(org,  6, 12, "monster_dog"        , monster_dog        , '-16 -16 -24', '16 16 32');
+	else if (r ==  3) spawnmonster(org,  6, 12, "monster_knight"     , monster_knight     , '-16 -16 -24', '16 16 32');
+	else if (r ==  4) spawnmonster(org,  3,  6, "monster_ogre"       , monster_ogre       , '-32 -32 -24', '32 32 64');
+	else if (r ==  5) spawnmonster(org,  1,  1, "monster_shambler"   , monster_shambler   , '-32 -32 -24', '32 32 64');
+	else if (r ==  6) spawnmonster(org,  6, 10, "monster_wizard"     , monster_wizard     , '-16 -16 -24', '16 16 32');
+	else if (r ==  7) spawnmonster(org,  8, 16, "monster_zombie"     , monster_zombie     , '-16 -16 -24', '16 16 32');
+	else if (r ==  8) spawnmonster(org,  4,  8, "monster_enforcer"   , monster_enforcer   , '-16 -16 -24', '16 16 32');
+	else if (r ==  9) spawnmonster(org,  4,  8, "monster_hell_knight", monster_hell_knight, '-16 -16 -24', '16 16 32');
+	else if (r == 10) spawnmonster(org,  1,  3, "monster_shalrath"   , monster_shalrath   , '-32 -32 -24', '32 32 64');
+	else if (r == 11) spawnmonster(org, 10, 15, "monster_tarbaby"    , monster_tarbaby    , '-16 -16 -24', '16 16 32');
+	else if (r == 12) spawnmonster(org,  4,  8, "monster_fish"       , monster_fish       , '-16 -16 -24', '16 16 32');
+};
+
+float monstersprecached;
+void() precachemonster_army;
+void() precachemonster_demon1;
+void() precachemonster_dog;
+void() precachemonster_enforcer;
+void() precachemonster_hell_knight;
+void() precachemonster_knight;
+void() precachemonster_ogre;
+void() precachemonster_shalrath;
+void() precachemonster_shambler;
+void() precachemonster_tarbaby;
+void() precachemonster_wizard;
+void() precachemonster_zombie;
+void() precachemonster_fish;
+
+void() precachemonsters =
+{
+	precachemonster_army();
+	precachemonster_demon1();
+	precachemonster_dog();
+	precachemonster_knight();
+	precachemonster_ogre();
+	precachemonster_shambler();
+	precachemonster_wizard();
+	precachemonster_zombie();
+	if (cvar("registered"))
+	{
+		precachemonster_enforcer();
+		precachemonster_hell_knight();
+		precachemonster_shalrath();
+		precachemonster_tarbaby();
+		precachemonster_fish();
+	}
+	monstersprecached = TRUE;
+};
+
+float spawnedexitmonsters;
+void() deathmatch7update =
+{
+	local entity e;
+	local float f, monster_count, monsters;
+	if (skill >= 5)
+	if (!deathmatch)
+	{
+		if (!spawnedexitmonsters)
+		if (time >= 2)
+		{
+			spawnedexitmonsters = TRUE;
+			e = find(world, classname, "trigger_changelevel");
+			while (e)
+			{
+				spawnmonster(e.origin + (e.mins + e.maxs) * 0.5, 8, 8, "monster_hellfish", monster_hellfish, '-16 -16 -24', '16 16 32');
+				e = find(e, classname, "trigger_changelevel");
+			}
+		}
+		return;
+	}
+	if (time < spawnchecktime)
+		return;
+	if (!monstersprecached)
+		return;
+	spawnchecktime = time + 0.2;
+	monsters = 0;
+	if (deathmatch == 7)
+		monsters = 50;
+	f = cvar("spawnmonsters");
+	if (f >= 1)
+		monsters = f;
+	if (monsters < 1)
+		return;
+	monster_count = 0;
+	e = findchainflags(flags, FL_MONSTER);
+	while (e)
+	{
+		monster_count = monster_count + 1;
+		e = e.chain;
+	}
+	if (monster_count >= monsters)
+		return;
+	spawnmonsters();
+}

Modified: trunk/data/qcsrc/server/progs.src
===================================================================
--- trunk/data/qcsrc/server/progs.src	2009-01-13 21:04:36 UTC (rev 5514)
+++ trunk/data/qcsrc/server/progs.src	2009-01-14 09:07:30 UTC (rev 5515)
@@ -157,3 +157,8 @@
 func_breakable.qc
 
 ../common/items.qc
+
+monsters/defs.qc
+monsters/fight.qc
+monsters/ai.qc
+monsters/m_monsters.qc

Modified: trunk/data/qcsrc/server/t_teleporters.qc
===================================================================
--- trunk/data/qcsrc/server/t_teleporters.qc	2009-01-13 21:04:36 UTC (rev 5514)
+++ trunk/data/qcsrc/server/t_teleporters.qc	2009-01-14 09:07:30 UTC (rev 5515)
@@ -1,3 +1,40 @@
+void tdeath(entity player, entity teleporter, entity telefragger, vector telefragmin, vector telefragmax)
+{
+	entity head;
+	vector deathmin;
+	vector deathmax;
+	float deathradius;
+	deathmin = player.absmin;
+	deathmax = player.absmax;
+	if(telefragmin != telefragmax)
+	{
+		if(deathmin_x > telefragmin_x) deathmin_x = telefragmin_x;
+		if(deathmin_y > telefragmin_y) deathmin_y = telefragmin_y;
+		if(deathmin_z > telefragmin_z) deathmin_z = telefragmin_z;
+		if(deathmax_x < telefragmax_x) deathmax_x = telefragmax_x;
+		if(deathmax_y < telefragmax_y) deathmax_y = telefragmax_y;
+		if(deathmax_z < telefragmax_z) deathmax_z = telefragmax_z;
+	}
+	deathradius = max(vlen(deathmin), vlen(deathmax));
+	for(head = findradius(player.origin, deathradius); head; head = head.chain)
+		if(head != player)
+			if(head.takedamage)
+				if(boxesoverlap(deathmin, deathmax, head.absmin, head.absmax))
+				{
+					if ((player.classname == "player") && (player.health >= 1))
+						Damage (head, teleporter, telefragger, 10000, DEATH_TELEFRAG, head.origin, '0 0 0');
+					else if (telefragger.health < 1) // corpses gib
+						Damage (head, teleporter, telefragger, 10000, DEATH_TELEFRAG, head.origin, '0 0 0');
+					else // dead bodies and monsters gib themselves instead of telefragging
+						Damage (telefragger, teleporter, telefragger, 10000, DEATH_TELEFRAG, telefragger.origin, '0 0 0');
+				}
+}
+
+void spawn_tdeath(vector v0, entity e, vector v)
+{
+	tdeath(e, e, e, '0 0 0', '0 0 0');
+}
+
 .entity pusher;
 void TeleportPlayer(entity teleporter, entity player, vector to, vector to_angles, vector to_velocity, vector telefragmin, vector telefragmax)
 {
@@ -4,6 +41,7 @@
 	entity head;
 	entity oldself;
 	entity telefragger;
+	vector from;
 
 	if(teleporter.owner)
 		telefragger = teleporter.owner;
@@ -22,6 +60,7 @@
 
 	// Relocate the player
 	// assuming to allows PL_MIN to PL_MAX box and some more
+	from = player.origin;
 	setorigin (player, to);
 	player.angles = to_angles;
 	player.fixangle = TRUE;
@@ -29,37 +68,8 @@
 
 	if(player.classname == "player")
 	{
-		// Kill anyone else in the teleporter box (NO MORE TDEATH)
 		if(player.takedamage && player.deadflag == DEAD_NO && !g_race)
-		{
-			vector deathmin;
-			vector deathmax;
-			float deathradius;
-			deathmin = player.absmin;
-			deathmax = player.absmax;
-			if(telefragmin != telefragmax)
-			{
-				if(deathmin_x > telefragmin_x) deathmin_x = telefragmin_x;
-				if(deathmin_y > telefragmin_y) deathmin_y = telefragmin_y;
-				if(deathmin_z > telefragmin_z) deathmin_z = telefragmin_z;
-				if(deathmax_x < telefragmax_x) deathmax_x = telefragmax_x;
-				if(deathmax_y < telefragmax_y) deathmax_y = telefragmax_y;
-				if(deathmax_z < telefragmax_z) deathmax_z = telefragmax_z;
-			}
-			deathradius = max(vlen(deathmin), vlen(deathmax));
-			for(head = findradius(player.origin, deathradius); head; head = head.chain)
-				if(head != player)
-					if(head.takedamage)
-						if(boxesoverlap(deathmin, deathmax, head.absmin, head.absmax))
-						{
-							if ((player.classname == "player") && (player.health >= 1))
-								Damage (head, teleporter, telefragger, 10000, DEATH_TELEFRAG, head.origin, '0 0 0');
-							else if (telefragger.health < 1) // corpses gib
-								Damage (head, teleporter, telefragger, 10000, DEATH_TELEFRAG, head.origin, '0 0 0');
-							else // dead bodies and monsters gib themselves instead of telefragging
-								Damage (telefragger, teleporter, telefragger, 10000, DEATH_TELEFRAG, telefragger.origin, '0 0 0');
-						}
-		}
+			tdeath(player, teleporter, telefragger, telefragmin, telefragmax);
 
 		// hide myself a tic
 		player.effects = player.effects | EF_NODRAW;




More information about the nexuiz-commits mailing list