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