--- /dev/null
+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 =
+{
+ 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;
+
+ /* PLEASE FIX THE SOUND CHANNEL BEFORE ACTIVATING THIS
+ 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 =
+{
+ vector v, v1;
+ 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
+
+ /* PLEASE FIX THE SOUND CHANNEL BEFORE ACTIVATING THIS
+ 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 =
+{
+//#NO AUTOCVARS START
+#if 0
+ float healthboost;
+ float r;
+
+ // 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
+//#NO AUTOCVARS END
+}
+
+
+//============================================================================
+
+/*
+=============
+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 =
+{
+ 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 =
+{
+ 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 =
+{
+ 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 =
+{
+ 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 =
+{
+ entity check;
+ 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 =
+{
+ entity client;
+ 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 =
+{
+ 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 =
+{
+ 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 =
+{
+ 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...
+}
+
--- /dev/null
+.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; // TODO Xonotic uses event_damage
+//.void() th_die; // TODO never called directly by Xonotic
+.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;
+//#NO AUTOCVARS START
+/*
+ monsterwander = cvar("monsterwander");
+ // monsterwander is always on in skill 5
+ if (skill >= 5)
+ monsterwander = TRUE;
+*/
+//#NO AUTOCVARS END
+
+.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;
--- /dev/null
+
+/*
+
+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 =
+{
+ vector spot1, spot2;
+ entity targ;
+ 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;
+ vector dtemp;
+ 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 =
+{
+ vector delta;
+ 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 =
+{
+ vector delta;
+ 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');
+}
+
--- /dev/null
+/* 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
+ /* PLEASE FIX THE SOUND CHANNEL BEFORE ACTIVATING THIS
+ 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
+ /* PLEASE FIX THE SOUND CHANNEL BEFORE ACTIVATING THIS
+ 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 =
+{
+ entity e;
+ if (!autocvar_developer)
+ return;
+ // this is handy for level designers,
+ // puts a spikey ball where the error is...
+ e = spawn();
+ setorigin(e, self.origin);
+ setmodel (e, "models/ebomb.mdl");
+ e.movetype = MOVETYPE_NONE;
+ e.solid = SOLID_NOT;
+ e.think = SUB_Null;
+ e.nextthink = -1;
+ e.scale = 16;
+}
+
+//============================================================================
+
+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;
+ self.damagedbycontents = 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;
+ self.damagedbycontents = 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;
+ self.damagedbycontents = 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 =
+{
+ 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);
+}
--- /dev/null
+//#define MONSTES_ENABLED
+#ifdef MONSTES_ENABLED
+
+float autocvar_g_monster_zombie_attack_run_damage;
+float autocvar_g_monster_zombie_attack_run_delay;
+float autocvar_g_monster_zombie_attack_run_force;
+float autocvar_g_monster_zombie_attack_run_hitrange;
+float autocvar_g_monster_zombie_attack_run_range;
+float autocvar_g_monster_zombie_attack_stand_damage;
+float autocvar_g_monster_zombie_attack_stand_delay;
+float autocvar_g_monster_zombie_attack_stand_force;
+float autocvar_g_monster_zombie_attack_stand_range;
+float autocvar_g_monster_zombie_health;
+float autocvar_g_monster_zombie_idle_timer_max;
+float autocvar_g_monster_zombie_idle_timer_min;
+float autocvar_g_monster_zombie_movespeed;
+float autocvar_g_monster_zombie_respawntime;
+float autocvar_g_monster_zombie_stopspeed;
+float autocvar_g_monster_zombie_targetrange;
+float autocvar_g_monster_zombie_turnspeed;
+float autocvar_g_monsters;
+
+
+#define zombie_anim_attackleap 0
+#define zombie_anim_attackrun1 1
+#define zombie_anim_attackrun2 2
+#define zombie_anim_attackrun3 3
+#define zombie_anim_attackstanding1 4
+#define zombie_anim_attackstanding2 5
+#define zombie_anim_attackstanding3 6
+#define zombie_anim_blockend 7
+#define zombie_anim_blockstart 8
+#define zombie_anim_deathback1 9
+#define zombie_anim_deathback2 10
+#define zombie_anim_deathback3 11
+#define zombie_anim_deathfront1 12
+#define zombie_anim_deathfront2 13
+#define zombie_anim_deathfront3 14
+#define zombie_anim_deathleft1 15
+#define zombie_anim_deathleft2 16
+#define zombie_anim_deathright1 17
+#define zombie_anim_deathright2 18
+#define zombie_anim_idle 19
+#define zombie_anim_painback1 20
+#define zombie_anim_painback2 21
+#define zombie_anim_painfront1 22
+#define zombie_anim_painfront2 23
+#define zombie_anim_runbackwards 24
+#define zombie_anim_runbackwardsleft 25
+#define zombie_anim_runbackwardsright 26
+#define zombie_anim_runforward 27
+#define zombie_anim_runforwardleft 28
+#define zombie_anim_runforwardright 29
+#define zombie_anim_spawn 30
+
+#define ZOMBIE_MIN '-18 -18 -25'
+#define ZOMBIE_MAX '18 18 47'
+
+#define ZV_IDLE 10
+
+#define ZV_PATH 100
+#define ZV_HUNT 200
+
+#define ZV_ATTACK_FIND 10
+#define ZV_ATTACK_RUN 20
+#define ZV_ATTACK_STAND 30
+
+#define ZV_PATH2 10000
+
+//.entity verbs_idle;
+//.entity verbs_attack;
+//.entity verbs_move;
+
+//.float state_timeout;
+//.void() monster_state;
+#define MONSTERFLAG_NORESPAWN 2
+
+void zombie_spawn();
+
+float zombie_scoretarget(entity trg)
+{
+ float tmp;
+ vector ang1;
+
+ if (trg.takedamage == DAMAGE_AIM)
+ if not (trg.flags & FL_NOTARGET)
+ if (trg.deadflag == DEAD_NO)
+ if (trg.team != self.team)
+ {
+ if((self.origin_z - trg.origin_z) < 128)
+ {
+ ang1 = normalize(self.origin - trg.origin);
+ tmp = vlen(ang1 - v_forward);
+ if(tmp > 1.5)
+ {
+ traceline(self.origin + '0 0 47',trg.origin + '0 0 32',MOVE_NORMAL,self);
+ if(trace_ent != trg)
+ return 0;
+
+ return (autocvar_g_monster_zombie_targetrange - vlen(self.origin - trg.origin)) * tmp;
+ }
+ else if(self.enemy == trg)
+ return (autocvar_g_monster_zombie_targetrange - vlen(self.origin - trg.origin)) * tmp;
+ }
+ }
+
+ return 0;
+}
+
+void zombie_corpse_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
+{
+ //dprint("zombie_corpse_damage\n");
+ Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, self, attacker);
+
+ self.health -= damage;
+
+ if(self.health < 0)
+ {
+ Violence_GibSplash(self, 1, 1, attacker);
+ remove(self);
+ }
+}
+
+void zombie_die(vector dir)
+{
+ vector v;
+ float f;
+
+ entity dummy;
+
+ dummy = spawn();
+ setmodel(dummy,"models/monsters/zombie.dpm");
+ setorigin(dummy, self.origin);
+ dummy.velocity = self.velocity;
+ dummy.movetype = MOVETYPE_BOUNCE;
+ dummy.think = SUB_Remove;
+ dummy.nextthink = time + 3;
+ dummy.health = 50;
+ dummy.takedamage = DAMAGE_YES;
+ dummy.event_damage = zombie_corpse_damage;
+ dummy.solid = SOLID_CORPSE;
+ setsize(dummy,self.mins,self.maxs);
+
+ SUB_SetFade(dummy,time + 5,2);
+
+
+ v = normalize(self.origin - dir);
+ f = vlen(v_forward - v) - 1;
+ if(f > 0.5)
+ dummy.frame = zombie_anim_deathfront1 + rint(random() * 2);
+ else if(f < 0.5)
+ dummy.frame = zombie_anim_deathback1 + rint(random() * 2);
+ else
+ {
+ f = vlen(v_right - v) - 1;
+ if(f > 0.5)
+ dummy.frame = zombie_anim_deathright1 + rint(random() * 2);
+ else if(f < 0.5)
+ dummy.frame = zombie_anim_deathleft1 + rint(random() * 2);
+ }
+
+
+ if(self.spawnflags & MONSTERFLAG_NORESPAWN)
+ {
+ self.think = SUB_Remove;
+ self.nextthink = time;
+ return;
+ }
+
+ setmodel(self,"");
+ self.solid = SOLID_NOT;
+ self.takedamage = DAMAGE_NO;
+ self.event_damage = SUB_Null;
+ self.enemy = world;
+ self.think = zombie_spawn;
+ self.nextthink = time + autocvar_g_monster_zombie_respawntime;
+ self.pain_finished = self.nextthink;
+}
+
+void zombie_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
+{
+
+ vector v;
+ float f;
+
+ v = normalize(self.origin - hitloc);
+ f = vlen(v_forward - v) - 1;
+
+
+ self.health -= damage;
+ self.velocity = self.velocity + force;
+ if(self.health <= 0)
+ {
+ zombie_die(hitloc);
+ return;
+ }
+
+ Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, self, attacker);
+
+ if (damage > 50)
+ Violence_GibSplash_At(hitloc, force * -0.1, 3, 1, self, attacker);
+ if (damage > 100)
+ Violence_GibSplash_At(hitloc, force * -0.2, 3, 1, self, attacker);
+
+ if (time > self.pain_finished)
+ {
+ if(f < 0.5)
+ {
+ if(random() < 0.5)
+ self.frame = zombie_anim_painback1;
+ else
+ self.frame = zombie_anim_painback2;
+ }
+ else
+ {
+ if(random() < 0.5)
+ self.frame = zombie_anim_painfront1;
+ else
+ self.frame = zombie_anim_painfront2;
+ }
+
+ self.pain_finished = time + 0.36;
+ }
+}
+
+.vector bvec;
+.float bvec_time;
+
+void zombie_move()
+{
+ vector real_angle;
+ float vz, tdiff, tspeed;
+
+ tdiff = time - self.zoomstate;
+ tspeed = tdiff * autocvar_g_monster_zombie_turnspeed;
+ vz = self.velocity_z;
+ self.zoomstate = time;
+
+ if(self.bvec_time < time)
+ {
+ self.bvec_time = time + 0.2;
+ self.bvec = steerlib_beamsteer(steerlib_attract2(self.moveto,0.5,500,0.95),512,32,34,64);
+ }
+
+ if(self.enemy)
+ self.moveto = self.enemy.origin;
+ else
+ self.moveto = self.origin + v_forward;
+
+ self.steerto = normalize(steerlib_attract2(self.moveto,0.5,500,0.95) + self.bvec);
+
+ self.angles_y = safeangle(self.angles_y);
+ real_angle = vectoangles(self.steerto) - self.angles;
+ self.angles_y += bound(-10, real_angle_y, 10);
+
+ if(vlen(self.origin - self.moveto) > 64)
+ {
+ movelib_move_simple(v_forward ,autocvar_g_monster_zombie_movespeed,0.6);
+ if(time > self.pain_finished)
+ if(self.attack_finished_single < time)
+ self.frame = zombie_anim_runforward;
+ }
+ else
+ {
+ movelib_beak_simple(autocvar_g_monster_zombie_stopspeed);
+ if(time > self.pain_finished)
+ if(self.attack_finished_single < time)
+ self.frame = zombie_anim_idle;
+ }
+
+ self.velocity_z = vz;
+ self.steerto = self.origin;
+}
+
+float zombie_verb_idle_roam(float eval)
+{
+ switch (eval)
+ {
+ case VCM_EVAL:
+
+ if(self.enemy)
+ return VS_CALL_NO;
+
+ return verb.verb_static_value;
+
+ case VCM_DO:
+
+ self.moveto = v_forward * 128;
+ self.steerto = v_forward; //steerlib_beamsteer(v_forward,512,32,34,64);
+
+ return VS_CALL_YES_DOING;
+ }
+
+ return VS_CALL_YES_DONE;
+}
+
+float zombie_verb_idle_stand(float eval)
+{
+ switch (eval)
+ {
+ case VCM_EVAL:
+
+ if(self.enemy)
+ return VS_CALL_NO;
+
+ return verb.verb_static_value;
+
+ case VCM_DO:
+
+ self.moveto = self.origin;
+ self.frame = zombie_anim_idle;
+ self.velocity = '0 0 0';
+
+ return VS_CALL_YES_DOING;
+ }
+
+ return VS_CALL_YES_DONE;
+}
+
+float zombie_verb_idle(float eval)
+{
+ switch (eval)
+ {
+ case VCM_EVAL:
+
+ if(self.enemy)
+ return VS_CALL_NO;
+
+ return verb.verb_static_value;
+
+ case VCM_DO:
+ float t;
+
+ t = autocvar_g_monster_zombie_idle_timer_max - autocvar_g_monster_zombie_idle_timer_min;
+ t = autocvar_g_monster_zombie_idle_timer_min + (random() * t);
+
+ if(random() < 0.5)
+ verbstack_push(self.verbs_idle, zombie_verb_idle_roam, ZV_IDLE + 1, t, self);
+ else
+ verbstack_push(self.verbs_idle, zombie_verb_idle_stand, ZV_IDLE + 1, 0.1, self);
+
+ return VS_CALL_YES_DOING;
+ }
+
+ return VS_CALL_YES_DONE;
+}
+
+float zombie_verb_attack_findtarget(float eval)
+{
+ switch (eval)
+ {
+ case VCM_EVAL:
+ if(self.enemy)
+ return VS_CALL_NO;
+
+ return verb.verb_static_value;
+
+ case VCM_DO:
+
+ entity trg, best_trg;
+ float trg_score, best_trg_score;
+
+ trg = findradius(self.origin,autocvar_g_monster_zombie_targetrange);
+ while(trg)
+ {
+ trg_score = zombie_scoretarget(trg);
+ if(trg_score > best_trg_score)
+ {
+ best_trg = trg;
+ best_trg_score = trg_score;
+ }
+
+ trg = trg.chain;
+ }
+
+ if(best_trg)
+ {
+ self.enemy = best_trg;
+ dprint("Selected: ",best_trg.netname, " as target.\n");
+ }
+
+ return VS_CALL_YES_DOING;
+ }
+
+ return VS_CALL_YES_DONE;
+}
+
+void zombie_runattack_damage()
+{
+ entity oldself;
+ oldself = self;
+ self = self.owner;
+
+ if(vlen(self.origin - self.enemy.origin) > autocvar_g_monster_zombie_attack_run_hitrange)
+ return;
+
+ if(vlen(normalize(self.origin - self.enemy.origin) - v_forward) < 1.6)
+ return;
+
+ Damage(self.enemy, self, self, autocvar_g_monster_zombie_attack_run_damage, DEATH_TURRET, self.enemy.origin, normalize(self.enemy.origin - self.origin) * autocvar_g_monster_zombie_attack_run_force);
+
+ self = oldself;
+ self.think = SUB_Remove;
+ self.nextthink = time;
+}
+
+float zombie_verb_attack_run(float eval)
+{
+ switch (eval)
+ {
+ case VCM_EVAL:
+ if not (self.enemy)
+ return VS_CALL_NO;
+
+ if(self.attack_finished_single > time)
+ return VS_CALL_NO;
+
+ if(vlen(self.origin - self.enemy.origin) > autocvar_g_monster_zombie_attack_run_range)
+ return VS_CALL_NO;
+
+ if(vlen(normalize(self.origin - self.enemy.origin) - v_forward) < 1.6)
+ return VS_CALL_NO;
+
+ return verb.verb_static_value;
+
+ case VCM_DO:
+ entity pain;
+ pain = spawn();
+ pain.owner = self;
+ pain.think = zombie_runattack_damage;
+ pain.nextthink = time + autocvar_g_monster_zombie_attack_run_delay;
+
+ self.attack_finished_single = time + 0.7;
+ self.frame = zombie_anim_attackrun1 + rint(random() * 2);
+
+ return VS_CALL_YES_DOING;
+ }
+
+ return VS_CALL_YES_DONE;
+}
+
+void zombie_standattack_damage()
+{
+ //entity oldself;
+ //oldself = self;
+ //self = self.owner;
+
+ setorigin(self,self.owner.origin + v_forward * 32);
+ RadiusDamage(self, self.owner, autocvar_g_monster_zombie_attack_stand_damage,autocvar_g_monster_zombie_attack_stand_damage,16,self, autocvar_g_monster_zombie_attack_stand_force,DEATH_TURRET,world);
+ //float RadiusDamage (entity inflictor, entity attacker, float coredamage, float edgedamage, float rad, entity ignore, float forceintensity, float deathtype, entity directhitentity)
+
+
+ //self = oldself;
+ self.think = SUB_Remove;
+ self.nextthink = time;
+}
+
+float zombie_verb_attack_stand(float eval)
+{
+ switch (eval)
+ {
+ case VCM_EVAL:
+ if not (self.enemy)
+ return VS_CALL_NO;
+
+ if(self.attack_finished_single > time)
+ return VS_CALL_NO;
+
+ if(vlen(self.origin - self.enemy.origin) > autocvar_g_monster_zombie_attack_stand_range)
+ return VS_CALL_NO;
+
+ if(vlen(normalize(self.origin - self.enemy.origin) - v_forward) < 1.8)
+ return VS_CALL_NO;
+
+ return verb.verb_static_value;
+
+ case VCM_DO:
+ entity pain;
+ pain = spawn();
+ pain.owner = self;
+ pain.think = zombie_runattack_damage;
+ pain.nextthink = time + autocvar_g_monster_zombie_attack_stand_delay;
+
+ self.attack_finished_single = time + 0.7;
+ self.frame = zombie_anim_attackstanding1 + rint(random() * 1);
+ dprint("frame:",ftos(self.frame),"\n");
+
+ return VS_CALL_YES_DOING;
+ }
+
+ return VS_CALL_YES_DONE;
+}
+
+void zombie_think()
+{
+ self.angles_x *= -1;
+ makevectors(self.angles);
+ self.angles_x *= -1;
+
+ if (zombie_scoretarget(self.enemy) == 0)
+ self.enemy = world;
+
+ verbstack_pop(self.verbs_attack);
+ //verbstack_pop(self.verbs_move);
+
+ if not (self.enemy)
+ verbstack_pop(self.verbs_idle);
+
+ zombie_move();
+
+ if(self.enemy)
+ self.nextthink = time;
+ else
+ self.nextthink = time + 0.2;
+}
+
+void zombie_spawn()
+{
+ setmodel(self,"models/monsters/zombie.dpm");
+
+ self.solid = SOLID_BBOX;
+ self.takedamage = DAMAGE_AIM;
+ self.event_damage = zombie_damage;
+ self.enemy = world;
+ self.frame = zombie_anim_spawn;
+ self.think = zombie_think;
+ self.nextthink = time + 2.1;
+ self.pain_finished = self.nextthink;
+ self.movetype = MOVETYPE_WALK;
+ self.health = autocvar_g_monster_zombie_health;
+ self.velocity = '0 0 0';
+ self.angles = self.pos2;
+ self.moveto = self.origin;
+ self.flags = FL_MONSTER;
+
+ setorigin(self,self.pos1);
+ setsize(self,ZOMBIE_MIN,ZOMBIE_MAX);
+}
+
+
+void spawnfunc_monster_zombie()
+{
+ if not(autocvar_g_monsters)
+ {
+ remove(self);
+ return;
+ }
+
+ precache_model("models/monsters/zombie.dpm");
+
+
+ self.verbs_idle = spawn();
+ self.verbs_attack = spawn();
+
+ self.verbs_idle.owner = self;
+ self.verbs_attack.owner = self;
+
+ self.think = zombie_spawn;
+ self.nextthink = time + 2;
+
+ traceline(self.origin + '0 0 10', self.origin - '0 0 32', MOVE_WORLDONLY, self);
+
+ self.pos1 = trace_endpos;
+ self.pos2 = self.angles;
+ self.team = MAX_SHOT_DISTANCE -1;
+
+ verbstack_push(self.verbs_idle, zombie_verb_idle, ZV_IDLE,0 , self);
+
+ verbstack_push(self.verbs_attack, zombie_verb_attack_findtarget, ZV_ATTACK_FIND,0 , self);
+ verbstack_push(self.verbs_attack, zombie_verb_attack_run, ZV_ATTACK_RUN,0 , self);
+ verbstack_push(self.verbs_attack, zombie_verb_attack_stand, ZV_ATTACK_STAND,0 , self);
+
+}
+
+#endif // MONSTES_ENABLED
float autocvar_g_minstagib_speed_highspeed;
#define autocvar_g_mirrordamage cvar("g_mirrordamage")
#define autocvar_g_mirrordamage_virtual cvar("g_mirrordamage_virtual")
-#ifdef WITH_ZOMBIE
-float autocvar_g_monster_zombie_attack_run_damage;
-float autocvar_g_monster_zombie_attack_run_delay;
-float autocvar_g_monster_zombie_attack_run_force;
-float autocvar_g_monster_zombie_attack_run_hitrange;
-float autocvar_g_monster_zombie_attack_run_range;
-float autocvar_g_monster_zombie_attack_stand_damage;
-float autocvar_g_monster_zombie_attack_stand_delay;
-float autocvar_g_monster_zombie_attack_stand_force;
-float autocvar_g_monster_zombie_attack_stand_range;
-float autocvar_g_monster_zombie_health;
-float autocvar_g_monster_zombie_idle_timer_max;
-float autocvar_g_monster_zombie_idle_timer_min;
-float autocvar_g_monster_zombie_movespeed;
-float autocvar_g_monster_zombie_respawntime;
-float autocvar_g_monster_zombie_stopspeed;
-float autocvar_g_monster_zombie_targetrange;
-float autocvar_g_monster_zombie_turnspeed;
-float autocvar_g_monsters;
-#endif
var float autocvar_g_movement_highspeed = 1;
float autocvar_g_multijump;
+++ /dev/null
-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 =
-{
- 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;
-
- /* PLEASE FIX THE SOUND CHANNEL BEFORE ACTIVATING THIS
- 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 =
-{
- vector v, v1;
- 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
-
- /* PLEASE FIX THE SOUND CHANNEL BEFORE ACTIVATING THIS
- 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 =
-{
-//#NO AUTOCVARS START
-#if 0
- float healthboost;
- float r;
-
- // 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
-//#NO AUTOCVARS END
-}
-
-
-//============================================================================
-
-/*
-=============
-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 =
-{
- 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 =
-{
- 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 =
-{
- 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 =
-{
- 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 =
-{
- entity check;
- 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 =
-{
- entity client;
- 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 =
-{
- 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 =
-{
- 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 =
-{
- 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...
-}
-
+++ /dev/null
-.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; // TODO Xonotic uses event_damage
-//.void() th_die; // TODO never called directly by Xonotic
-.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;
-//#NO AUTOCVARS START
-/*
- monsterwander = cvar("monsterwander");
- // monsterwander is always on in skill 5
- if (skill >= 5)
- monsterwander = TRUE;
-*/
-//#NO AUTOCVARS END
-
-.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;
+++ /dev/null
-
-/*
-
-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 =
-{
- vector spot1, spot2;
- entity targ;
- 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;
- vector dtemp;
- 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 =
-{
- vector delta;
- 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 =
-{
- vector delta;
- 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');
-}
-
+++ /dev/null
-/* 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
- /* PLEASE FIX THE SOUND CHANNEL BEFORE ACTIVATING THIS
- 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
- /* PLEASE FIX THE SOUND CHANNEL BEFORE ACTIVATING THIS
- 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 =
-{
- entity e;
- if (!autocvar_developer)
- return;
- // this is handy for level designers,
- // puts a spikey ball where the error is...
- e = spawn();
- setorigin(e, self.origin);
- setmodel (e, "models/ebomb.mdl");
- e.movetype = MOVETYPE_NONE;
- e.solid = SOLID_NOT;
- e.think = SUB_Null;
- e.nextthink = -1;
- e.scale = 16;
-}
-
-//============================================================================
-
-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;
- self.damagedbycontents = 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;
- self.damagedbycontents = 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;
- self.damagedbycontents = 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 =
-{
- 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);
-}
+++ /dev/null
-//#define MONSTES_ENABLED
-#ifdef MONSTES_ENABLED
-
-#define zombie_anim_attackleap 0
-#define zombie_anim_attackrun1 1
-#define zombie_anim_attackrun2 2
-#define zombie_anim_attackrun3 3
-#define zombie_anim_attackstanding1 4
-#define zombie_anim_attackstanding2 5
-#define zombie_anim_attackstanding3 6
-#define zombie_anim_blockend 7
-#define zombie_anim_blockstart 8
-#define zombie_anim_deathback1 9
-#define zombie_anim_deathback2 10
-#define zombie_anim_deathback3 11
-#define zombie_anim_deathfront1 12
-#define zombie_anim_deathfront2 13
-#define zombie_anim_deathfront3 14
-#define zombie_anim_deathleft1 15
-#define zombie_anim_deathleft2 16
-#define zombie_anim_deathright1 17
-#define zombie_anim_deathright2 18
-#define zombie_anim_idle 19
-#define zombie_anim_painback1 20
-#define zombie_anim_painback2 21
-#define zombie_anim_painfront1 22
-#define zombie_anim_painfront2 23
-#define zombie_anim_runbackwards 24
-#define zombie_anim_runbackwardsleft 25
-#define zombie_anim_runbackwardsright 26
-#define zombie_anim_runforward 27
-#define zombie_anim_runforwardleft 28
-#define zombie_anim_runforwardright 29
-#define zombie_anim_spawn 30
-
-#define ZOMBIE_MIN '-18 -18 -25'
-#define ZOMBIE_MAX '18 18 47'
-
-#define ZV_IDLE 10
-
-#define ZV_PATH 100
-#define ZV_HUNT 200
-
-#define ZV_ATTACK_FIND 10
-#define ZV_ATTACK_RUN 20
-#define ZV_ATTACK_STAND 30
-
-#define ZV_PATH2 10000
-
-//.entity verbs_idle;
-//.entity verbs_attack;
-//.entity verbs_move;
-
-//.float state_timeout;
-//.void() monster_state;
-#define MONSTERFLAG_NORESPAWN 2
-
-void zombie_spawn();
-
-float zombie_scoretarget(entity trg)
-{
- float tmp;
- vector ang1;
-
- if (trg.takedamage == DAMAGE_AIM)
- if not (trg.flags & FL_NOTARGET)
- if (trg.deadflag == DEAD_NO)
- if (trg.team != self.team)
- {
- if((self.origin_z - trg.origin_z) < 128)
- {
- ang1 = normalize(self.origin - trg.origin);
- tmp = vlen(ang1 - v_forward);
- if(tmp > 1.5)
- {
- traceline(self.origin + '0 0 47',trg.origin + '0 0 32',MOVE_NORMAL,self);
- if(trace_ent != trg)
- return 0;
-
- return (autocvar_g_monster_zombie_targetrange - vlen(self.origin - trg.origin)) * tmp;
- }
- else if(self.enemy == trg)
- return (autocvar_g_monster_zombie_targetrange - vlen(self.origin - trg.origin)) * tmp;
- }
- }
-
- return 0;
-}
-
-void zombie_corpse_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
-{
- //dprint("zombie_corpse_damage\n");
- Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, self, attacker);
-
- self.health -= damage;
-
- if(self.health < 0)
- {
- Violence_GibSplash(self, 1, 1, attacker);
- remove(self);
- }
-}
-
-void zombie_die(vector dir)
-{
- vector v;
- float f;
-
- entity dummy;
-
- dummy = spawn();
- setmodel(dummy,"models/monsters/zombie.dpm");
- setorigin(dummy, self.origin);
- dummy.velocity = self.velocity;
- dummy.movetype = MOVETYPE_BOUNCE;
- dummy.think = SUB_Remove;
- dummy.nextthink = time + 3;
- dummy.health = 50;
- dummy.takedamage = DAMAGE_YES;
- dummy.event_damage = zombie_corpse_damage;
- dummy.solid = SOLID_CORPSE;
- setsize(dummy,self.mins,self.maxs);
-
- SUB_SetFade(dummy,time + 5,2);
-
-
- v = normalize(self.origin - dir);
- f = vlen(v_forward - v) - 1;
- if(f > 0.5)
- dummy.frame = zombie_anim_deathfront1 + rint(random() * 2);
- else if(f < 0.5)
- dummy.frame = zombie_anim_deathback1 + rint(random() * 2);
- else
- {
- f = vlen(v_right - v) - 1;
- if(f > 0.5)
- dummy.frame = zombie_anim_deathright1 + rint(random() * 2);
- else if(f < 0.5)
- dummy.frame = zombie_anim_deathleft1 + rint(random() * 2);
- }
-
-
- if(self.spawnflags & MONSTERFLAG_NORESPAWN)
- {
- self.think = SUB_Remove;
- self.nextthink = time;
- return;
- }
-
- setmodel(self,"");
- self.solid = SOLID_NOT;
- self.takedamage = DAMAGE_NO;
- self.event_damage = SUB_Null;
- self.enemy = world;
- self.think = zombie_spawn;
- self.nextthink = time + autocvar_g_monster_zombie_respawntime;
- self.pain_finished = self.nextthink;
-}
-
-void zombie_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
-{
-
- vector v;
- float f;
-
- v = normalize(self.origin - hitloc);
- f = vlen(v_forward - v) - 1;
-
-
- self.health -= damage;
- self.velocity = self.velocity + force;
- if(self.health <= 0)
- {
- zombie_die(hitloc);
- return;
- }
-
- Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, self, attacker);
-
- if (damage > 50)
- Violence_GibSplash_At(hitloc, force * -0.1, 3, 1, self, attacker);
- if (damage > 100)
- Violence_GibSplash_At(hitloc, force * -0.2, 3, 1, self, attacker);
-
- if (time > self.pain_finished)
- {
- if(f < 0.5)
- {
- if(random() < 0.5)
- self.frame = zombie_anim_painback1;
- else
- self.frame = zombie_anim_painback2;
- }
- else
- {
- if(random() < 0.5)
- self.frame = zombie_anim_painfront1;
- else
- self.frame = zombie_anim_painfront2;
- }
-
- self.pain_finished = time + 0.36;
- }
-}
-
-.vector bvec;
-.float bvec_time;
-
-void zombie_move()
-{
- vector real_angle;
- float vz, tdiff, tspeed;
-
- tdiff = time - self.zoomstate;
- tspeed = tdiff * autocvar_g_monster_zombie_turnspeed;
- vz = self.velocity_z;
- self.zoomstate = time;
-
- if(self.bvec_time < time)
- {
- self.bvec_time = time + 0.2;
- self.bvec = steerlib_beamsteer(steerlib_attract2(self.moveto,0.5,500,0.95),512,32,34,64);
- }
-
- if(self.enemy)
- self.moveto = self.enemy.origin;
- else
- self.moveto = self.origin + v_forward;
-
- self.steerto = normalize(steerlib_attract2(self.moveto,0.5,500,0.95) + self.bvec);
-
- self.angles_y = safeangle(self.angles_y);
- real_angle = vectoangles(self.steerto) - self.angles;
- self.angles_y += bound(-10, real_angle_y, 10);
-
- if(vlen(self.origin - self.moveto) > 64)
- {
- movelib_move_simple(v_forward ,autocvar_g_monster_zombie_movespeed,0.6);
- if(time > self.pain_finished)
- if(self.attack_finished_single < time)
- self.frame = zombie_anim_runforward;
- }
- else
- {
- movelib_beak_simple(autocvar_g_monster_zombie_stopspeed);
- if(time > self.pain_finished)
- if(self.attack_finished_single < time)
- self.frame = zombie_anim_idle;
- }
-
- self.velocity_z = vz;
- self.steerto = self.origin;
-}
-
-float zombie_verb_idle_roam(float eval)
-{
- switch (eval)
- {
- case VCM_EVAL:
-
- if(self.enemy)
- return VS_CALL_NO;
-
- return verb.verb_static_value;
-
- case VCM_DO:
-
- self.moveto = v_forward * 128;
- self.steerto = v_forward; //steerlib_beamsteer(v_forward,512,32,34,64);
-
- return VS_CALL_YES_DOING;
- }
-
- return VS_CALL_YES_DONE;
-}
-
-float zombie_verb_idle_stand(float eval)
-{
- switch (eval)
- {
- case VCM_EVAL:
-
- if(self.enemy)
- return VS_CALL_NO;
-
- return verb.verb_static_value;
-
- case VCM_DO:
-
- self.moveto = self.origin;
- self.frame = zombie_anim_idle;
- self.velocity = '0 0 0';
-
- return VS_CALL_YES_DOING;
- }
-
- return VS_CALL_YES_DONE;
-}
-
-float zombie_verb_idle(float eval)
-{
- switch (eval)
- {
- case VCM_EVAL:
-
- if(self.enemy)
- return VS_CALL_NO;
-
- return verb.verb_static_value;
-
- case VCM_DO:
- float t;
-
- t = autocvar_g_monster_zombie_idle_timer_max - autocvar_g_monster_zombie_idle_timer_min;
- t = autocvar_g_monster_zombie_idle_timer_min + (random() * t);
-
- if(random() < 0.5)
- verbstack_push(self.verbs_idle, zombie_verb_idle_roam, ZV_IDLE + 1, t, self);
- else
- verbstack_push(self.verbs_idle, zombie_verb_idle_stand, ZV_IDLE + 1, 0.1, self);
-
- return VS_CALL_YES_DOING;
- }
-
- return VS_CALL_YES_DONE;
-}
-
-float zombie_verb_attack_findtarget(float eval)
-{
- switch (eval)
- {
- case VCM_EVAL:
- if(self.enemy)
- return VS_CALL_NO;
-
- return verb.verb_static_value;
-
- case VCM_DO:
-
- entity trg, best_trg;
- float trg_score, best_trg_score;
-
- trg = findradius(self.origin,autocvar_g_monster_zombie_targetrange);
- while(trg)
- {
- trg_score = zombie_scoretarget(trg);
- if(trg_score > best_trg_score)
- {
- best_trg = trg;
- best_trg_score = trg_score;
- }
-
- trg = trg.chain;
- }
-
- if(best_trg)
- {
- self.enemy = best_trg;
- dprint("Selected: ",best_trg.netname, " as target.\n");
- }
-
- return VS_CALL_YES_DOING;
- }
-
- return VS_CALL_YES_DONE;
-}
-
-void zombie_runattack_damage()
-{
- entity oldself;
- oldself = self;
- self = self.owner;
-
- if(vlen(self.origin - self.enemy.origin) > autocvar_g_monster_zombie_attack_run_hitrange)
- return;
-
- if(vlen(normalize(self.origin - self.enemy.origin) - v_forward) < 1.6)
- return;
-
- Damage(self.enemy, self, self, autocvar_g_monster_zombie_attack_run_damage, DEATH_TURRET, self.enemy.origin, normalize(self.enemy.origin - self.origin) * autocvar_g_monster_zombie_attack_run_force);
-
- self = oldself;
- self.think = SUB_Remove;
- self.nextthink = time;
-}
-
-float zombie_verb_attack_run(float eval)
-{
- switch (eval)
- {
- case VCM_EVAL:
- if not (self.enemy)
- return VS_CALL_NO;
-
- if(self.attack_finished_single > time)
- return VS_CALL_NO;
-
- if(vlen(self.origin - self.enemy.origin) > autocvar_g_monster_zombie_attack_run_range)
- return VS_CALL_NO;
-
- if(vlen(normalize(self.origin - self.enemy.origin) - v_forward) < 1.6)
- return VS_CALL_NO;
-
- return verb.verb_static_value;
-
- case VCM_DO:
- entity pain;
- pain = spawn();
- pain.owner = self;
- pain.think = zombie_runattack_damage;
- pain.nextthink = time + autocvar_g_monster_zombie_attack_run_delay;
-
- self.attack_finished_single = time + 0.7;
- self.frame = zombie_anim_attackrun1 + rint(random() * 2);
-
- return VS_CALL_YES_DOING;
- }
-
- return VS_CALL_YES_DONE;
-}
-
-void zombie_standattack_damage()
-{
- //entity oldself;
- //oldself = self;
- //self = self.owner;
-
- setorigin(self,self.owner.origin + v_forward * 32);
- RadiusDamage(self, self.owner, autocvar_g_monster_zombie_attack_stand_damage,autocvar_g_monster_zombie_attack_stand_damage,16,self, autocvar_g_monster_zombie_attack_stand_force,DEATH_TURRET,world);
- //float RadiusDamage (entity inflictor, entity attacker, float coredamage, float edgedamage, float rad, entity ignore, float forceintensity, float deathtype, entity directhitentity)
-
-
- //self = oldself;
- self.think = SUB_Remove;
- self.nextthink = time;
-}
-
-float zombie_verb_attack_stand(float eval)
-{
- switch (eval)
- {
- case VCM_EVAL:
- if not (self.enemy)
- return VS_CALL_NO;
-
- if(self.attack_finished_single > time)
- return VS_CALL_NO;
-
- if(vlen(self.origin - self.enemy.origin) > autocvar_g_monster_zombie_attack_stand_range)
- return VS_CALL_NO;
-
- if(vlen(normalize(self.origin - self.enemy.origin) - v_forward) < 1.8)
- return VS_CALL_NO;
-
- return verb.verb_static_value;
-
- case VCM_DO:
- entity pain;
- pain = spawn();
- pain.owner = self;
- pain.think = zombie_runattack_damage;
- pain.nextthink = time + autocvar_g_monster_zombie_attack_stand_delay;
-
- self.attack_finished_single = time + 0.7;
- self.frame = zombie_anim_attackstanding1 + rint(random() * 1);
- dprint("frame:",ftos(self.frame),"\n");
-
- return VS_CALL_YES_DOING;
- }
-
- return VS_CALL_YES_DONE;
-}
-
-void zombie_think()
-{
- self.angles_x *= -1;
- makevectors(self.angles);
- self.angles_x *= -1;
-
- if (zombie_scoretarget(self.enemy) == 0)
- self.enemy = world;
-
- verbstack_pop(self.verbs_attack);
- //verbstack_pop(self.verbs_move);
-
- if not (self.enemy)
- verbstack_pop(self.verbs_idle);
-
- zombie_move();
-
- if(self.enemy)
- self.nextthink = time;
- else
- self.nextthink = time + 0.2;
-}
-
-void zombie_spawn()
-{
- setmodel(self,"models/monsters/zombie.dpm");
-
- self.solid = SOLID_BBOX;
- self.takedamage = DAMAGE_AIM;
- self.event_damage = zombie_damage;
- self.enemy = world;
- self.frame = zombie_anim_spawn;
- self.think = zombie_think;
- self.nextthink = time + 2.1;
- self.pain_finished = self.nextthink;
- self.movetype = MOVETYPE_WALK;
- self.health = autocvar_g_monster_zombie_health;
- self.velocity = '0 0 0';
- self.angles = self.pos2;
- self.moveto = self.origin;
- self.flags = FL_MONSTER;
-
- setorigin(self,self.pos1);
- setsize(self,ZOMBIE_MIN,ZOMBIE_MAX);
-}
-
-
-void spawnfunc_monster_zombie()
-{
- if not(autocvar_g_monsters)
- {
- remove(self);
- return;
- }
-
- precache_model("models/monsters/zombie.dpm");
-
-
- self.verbs_idle = spawn();
- self.verbs_attack = spawn();
-
- self.verbs_idle.owner = self;
- self.verbs_attack.owner = self;
-
- self.think = zombie_spawn;
- self.nextthink = time + 2;
-
- traceline(self.origin + '0 0 10', self.origin - '0 0 32', MOVE_WORLDONLY, self);
-
- self.pos1 = trace_endpos;
- self.pos2 = self.angles;
- self.team = MAX_SHOT_DISTANCE -1;
-
- verbstack_push(self.verbs_idle, zombie_verb_idle, ZV_IDLE,0 , self);
-
- verbstack_push(self.verbs_attack, zombie_verb_attack_findtarget, ZV_ATTACK_FIND,0 , self);
- verbstack_push(self.verbs_attack, zombie_verb_attack_run, ZV_ATTACK_RUN,0 , self);
- verbstack_push(self.verbs_attack, zombie_verb_attack_stand, ZV_ATTACK_STAND,0 , self);
-
-}
-
-#endif // MONSTES_ENABLED
../common/items.qc
-monsters/defs.qc
-monsters/fight.qc
-monsters/ai.qc
-monsters/m_monsters.qc
+//monsters/defs.qc
+//monsters/fight.qc
+//monsters/ai.qc
+//monsters/m_monsters.qc
//monsters/monster_zombie.qc
accuracy.qc
csqcprojectile.qc