// Spawner
set g_monster_spawner 1 "Enable Monster Spawner"
set g_monster_spawner_health 100 "Spawner health"
-set g_monster_spawner_target_recheck_delay 1 "Spawner enemy check delay"
-set g_monster_spawner_target_range 600 "Spawner maximum enemy distance"
-set g_monster_spawner_spawn_range 600 "Spawn monsters while enemy is within this range"
set g_monster_spawner_maxmobs 4 "Maximum number of spawned monsters"
-set g_monster_spawner_forcespawn 0 "Force spawner to spawn this type of monster"
+set g_monster_spawner_forcespawn "" "Force spawner to spawn this type of monster"
// Zombie
set g_monster_zombie 1 "Enable Zombies"
set g_monster_zombie_attack_stand_delay 1.2 "Delay after a zombie hits from a standing position"
set g_monster_zombie_attack_stand_range 48 "Range of a zombie standing position attack"
set g_monster_zombie_health 200 "Zombie health"
-set g_monster_zombie_idle_timer 1 "Minimum time a zombie can stay idle"
set g_monster_zombie_speed_walk 150 "Zombie walk speed"
set g_monster_zombie_speed_run 400 "Zombie run speed"
set g_monster_zombie_stopspeed 100 "Speed at which zombie stops"
-set g_monster_zombie_target_recheck_delay 5 How much time should a zombie run afer an enemy before checking if it's still in range"
-set g_monster_zombie_target_range 1200 How far the zombie can see an enemy"
set g_monster_zombie_drop health "Zombie drops this item on death"
set g_monster_zombie_drop_size large "Size of the item zombies drop. Possible values are: small, medium, large"
.void() monster_die;
.void() monster_delayedattack;
-.float monster_moveflags; // checks where to move when not attacking (currently unused)
+.float monster_moveflags; // checks where to move when not attacking
+.float monster_movestate; // used to tell what the monster is currently doing
const float MONSTER_MOVE_OWNER = 1; // monster will move to owner if in range, or stand still
const float MONSTER_MOVE_WANDER = 2; // monster will ignore owner & wander around
const float MONSTER_MOVE_SPAWNLOC = 3; // monster will move to its spawn location when not attacking
const float MONSTER_MOVE_NOMOVE = 4; // monster simply stands still
+const float MONSTER_MOVE_ENEMY = 5; // used only as a movestate
float enemy_range () { return vlen(self.enemy.origin - self.origin); }
vector monster_pickmovetarget(entity targ)
{
// enemy is always preferred target
- if(self.enemy && trace_path(self.origin + '0 0 10', self.enemy.origin + '0 0 10') > 0.99)
- return self.enemy.origin + 60 * normalize(self.enemy.origin - self.origin);
+ if(self.enemy)
+ {
+ self.monster_movestate = MONSTER_MOVE_ENEMY;
+ return self.enemy.origin;
+ }
switch(self.monster_moveflags)
{
case MONSTER_MOVE_OWNER:
{
- if(self.monster_owner && self.monster_owner.classname != "monster_swarm" && trace_path(self.origin + '0 0 10', self.monster_owner.origin + '0 0 10') > 0.99)
+ self.monster_movestate = MONSTER_MOVE_OWNER;
+ if(self.monster_owner && self.monster_owner.classname != "monster_swarm")
return self.monster_owner.origin;
}
case MONSTER_MOVE_WANDER:
{
+ self.monster_movestate = MONSTER_MOVE_WANDER;
if(targ)
return targ.origin;
return self.origin + v_forward * 600;
}
case MONSTER_MOVE_SPAWNLOC:
+ {
+ self.monster_movestate = MONSTER_MOVE_SPAWNLOC;
return self.pos1;
+ }
default:
case MONSTER_MOVE_NOMOVE:
+ {
+ self.monster_movestate = MONSTER_MOVE_NOMOVE;
return self.origin;
+ }
}
}
if(time >= self.last_trace)
{
- if(self.monster_moveflags & MONSTER_MOVE_WANDER)
+ if(self.monster_movestate == MONSTER_MOVE_WANDER)
self.last_trace = time + 2;
else
self.last_trace = time + 0.5;
if(self.flags & FL_ONGROUND)
movelib_jump_simple(100);
- if(vlen(self.moveto - self.origin) > 64)
+ if(vlen(self.origin - self.moveto) > 64)
{
if(self.flags & FL_FLY)
movelib_move_simple(v_forward, ((self.enemy) ? runspeed : walkspeed), 0.6);
else
movelib_move_simple_gravity(v_forward, ((self.enemy) ? runspeed : walkspeed), 0.6);
if(time > self.pain_finished)
- if(time > self.attack_finished_single)
- self.frame = ((self.enemy) ? manim_run : manim_walk);
+ if(time > self.attack_finished_single)
+ self.frame = ((self.enemy) ? manim_run : manim_walk);
}
else
{
movelib_beak_simple(stopspeed);
- if(time > self.attack_finished_single)
- if(time > self.pain_finished)
- if (vlen(self.velocity) <= 30)
- self.frame = manim_idle;
+ if(time > self.attack_finished_single)
+ if(time > self.pain_finished)
+ if (vlen(self.velocity) <= 30)
+ self.frame = manim_idle;
}
if(self.enemy)
entity FindTarget (entity ent)
{
- if(MUTATOR_CALLHOOK(MonsterFindTarget)) { return self.goalentity; } // Handled by a mutator
+ if(MUTATOR_CALLHOOK(MonsterFindTarget)) { return ent.goalentity; } // Handled by a mutator
local entity e;
for(e = world; (e = findflags(e, monster_attack, TRUE)); )
{
// cvars
float autocvar_g_monster_spawner;
float autocvar_g_monster_spawner_health;
-float autocvar_g_monster_spawner_target_recheck_delay;
-float autocvar_g_monster_spawner_target_range;
-float autocvar_g_monster_spawner_spawn_range;
float autocvar_g_monster_spawner_maxmobs;
string autocvar_g_monster_spawner_forcespawn;
if(self.spawner_monstercount >= autocvar_g_monster_spawner_maxmobs || self.frozen || self.freezetag_frozen)
return;
- vector posi1 = '0 0 0', posi2 = '0 0 0', posi3 = '0 0 0', posi4 = '0 0 0', chosenposi = '0 0 0';
+ vector p1, p2, p3, p4, chosenposi;
float r = random();
- string type = string_null;
- entity e = world;
+ string type = "";
+ entity e;
self.spawner_monstercount += 1;
if(self.spawnmob != "")
type = self.spawnmob;
- if(autocvar_g_monster_spawner_forcespawn != "0")
+ if(autocvar_g_monster_spawner_forcespawn != "")
type = autocvar_g_monster_spawner_forcespawn;
if(type == "" || type == "spawner") // spawner spawning spawners?!
type = "knight";
- posi1 = self.origin - '0 70 -50' * self.scale;
- posi2 = self.origin + '0 70 50' * self.scale;
- posi3 = self.origin - '70 0 -50' * self.scale;
- posi4 = self.origin + '70 0 -50' * self.scale;
+ p1 = self.origin - '0 70 -50' * self.scale;
+ p2 = self.origin + '0 70 50' * self.scale;
+ p3 = self.origin - '70 0 -50' * self.scale;
+ p4 = self.origin + '70 0 -50' * self.scale;
if (r < 0.20)
- chosenposi = posi1;
+ chosenposi = p1;
else if (r < 0.50)
- chosenposi = posi2;
+ chosenposi = p2;
else if (r < 80)
- chosenposi = posi3;
+ chosenposi = p3;
else
- chosenposi = posi4;
+ chosenposi = p4;
e = spawnmonster(type, self, self, chosenposi, FALSE, MONSTER_MOVE_WANDER);
void spawner_think()
{
- float finished = FALSE, enemyDistance = 0;
+ float finished = FALSE;
self.think = spawner_think;
- if(self.spawner_monstercount == autocvar_g_monster_spawner_maxmobs)
+ if(self.spawner_monstercount >= autocvar_g_monster_spawner_maxmobs)
{
self.think = spawner_recount;
- self.nextthink = time + 20;
+ self.nextthink = time + 10 + random() * 4;
return;
}
-
- // remove enemy that ran away
- if (self.enemy)
- if (self.delay <= time) // check if we can do the rescan now
- if (vlen(self.origin - self.enemy.origin) > autocvar_g_monster_spawner_target_range * self.scale)
- self.enemy = world;
- else
- self.delay = time + autocvar_g_monster_spawner_target_recheck_delay;
-
- if not(self.enemy)
- {
- self.enemy = FindTarget(self);
- if (self.enemy)
- self.delay = time + autocvar_g_monster_spawner_target_recheck_delay;
- }
- if (self.enemy)
+ if (self.spawner_monstercount <= autocvar_g_monster_spawner_maxmobs)
{
- // this spawner has an enemy
- traceline(self.origin, self.enemy.origin, FALSE, self);
- enemyDistance = vlen(trace_endpos - self.origin);
-
- if (trace_ent == self.enemy)
- if (self.enemy.deadflag == DEAD_NO)
- if (self.spawner_monstercount <= autocvar_g_monster_spawner_maxmobs)
- if (enemyDistance <= autocvar_g_monster_spawner_spawn_range * self.scale)
- {
- spawnmonsters();
- finished = TRUE;
- }
- }
+ spawnmonsters();
+ finished = TRUE;
+ }
self.nextthink = time + 1;
self.nextthink = time + 0.1;
if not(finished)
- {
- if (self.enemy)
- self.nextthink = time + 0.1;
- }
+ self.nextthink = time + 0.1;
}
void spawner_spawn()
self.health = autocvar_g_monster_spawner_health * self.scale;
self.classname = "monster_spawner";
- self.nextthink = time + 2.1;
+ self.nextthink = time + 0.2;
self.velocity = '0 0 0';
self.think = spawner_think;
self.touch = func_null;
* Special purpose fields:
* .delay - time at which to check if zombie's enemy is still in range
* .enemy - enemy of this zombie
- * .state - state of the zombie, see ZOMBIE_STATE_*
*/
// cvars
float autocvar_g_monster_zombie_attack_leap_damage;
float autocvar_g_monster_zombie_attack_leap_delay;
float autocvar_g_monster_zombie_attack_leap_force;
-float autocvar_g_monster_zombie_attack_leap_range;
float autocvar_g_monster_zombie_attack_leap_speed;
float autocvar_g_monster_zombie_attack_stand_damage;
float autocvar_g_monster_zombie_attack_stand_delay;
-float autocvar_g_monster_zombie_attack_stand_range;
float autocvar_g_monster_zombie_health;
-float autocvar_g_monster_zombie_idle_timer;
float autocvar_g_monster_zombie_speed_walk;
float autocvar_g_monster_zombie_speed_run;
-float autocvar_g_monster_zombie_target_recheck_delay;
-float autocvar_g_monster_zombie_target_range;
// zombie animations
#define zombie_anim_attackleap 0
const vector ZOMBIE_MIN = '-18 -18 -25';
const vector ZOMBIE_MAX = '18 18 47';
-#define ZOMBIE_STATE_SPAWNING 0
-#define ZOMBIE_STATE_IDLE 1
-#define ZOMBIE_STATE_ANGRY 2
-#define ZOMBIE_STATE_ATTACK_LEAP 3
-
void zombie_spawn();
void spawnfunc_monster_zombie();
void zombie_think();
self.frame = zombie_anim_attackstanding3;
self.nextthink = time + autocvar_g_monster_zombie_attack_stand_delay;
+ self.attack_finished_single = self.nextthink;
}
void zombie_attack_leap_touch()
{
- vector angles_face = '0 0 0';
+ vector angles_face;
float bigdmg = autocvar_g_monster_zombie_attack_leap_damage * self.scale;
if (other.deadflag != DEAD_NO)
angles_face = vectoangles(self.moveto - self.origin);
angles_face = normalize(angles_face) * autocvar_g_monster_zombie_attack_leap_force;
Damage(other, self, self, bigdmg * monster_skill, DEATH_MONSTER_MELEE, trace_endpos, angles_face);
-
- // make this guy zombie's priority if it wasn't already
- if (other.deadflag == DEAD_NO)
- if (self.enemy != other)
- self.enemy = other;
self.touch = MonsterTouch;
}
-void zombie_attack_leap()
+float zombie_attack_ranged()
{
- vector angles_face = '0 0 0', vel = '0 0 0';
-
- // face the enemy
- self.state = ZOMBIE_STATE_ATTACK_LEAP;
- self.frame = zombie_anim_attackleap;
- angles_face = vectoangles(self.enemy.origin - self.origin);
- self.angles_y = angles_face_y ;
- self.nextthink = time + autocvar_g_monster_zombie_attack_leap_delay;
- self.touch = zombie_attack_leap_touch;
makevectors(self.angles);
- vel = normalize(v_forward);
- self.velocity = vel * autocvar_g_monster_zombie_attack_leap_speed;
+ if(monster_leap(zombie_anim_attackleap, zombie_attack_leap_touch, v_forward * autocvar_g_monster_zombie_attack_leap_speed + '0 0 200', autocvar_g_monster_zombie_attack_leap_delay))
+ return TRUE;
+
+ return FALSE;
}
void zombie_think()
{
- float finished = FALSE, enemyDistance = 0, mySpeed = 0;
-
self.think = zombie_think;
-
- if (self.state == ZOMBIE_STATE_ATTACK_LEAP) {
- // reset to angry
- self.state = ZOMBIE_STATE_ANGRY;
- self.touch = func_null;
- }
-
- if (self.state == ZOMBIE_STATE_SPAWNING) {
- // become idle when zombie spawned
- self.frame = zombie_anim_idle;
- self.state = ZOMBIE_STATE_IDLE;
- }
-
- if(self.enemy && !monster_isvalidtarget(self.enemy, self, FALSE))
- self.enemy = world;
-
- if (self.enemy)
- if (self.enemy.team == self.team || self.monster_owner == self.enemy)
- self.enemy = world;
-
- if(teamplay && autocvar_g_monsters_teams && self.monster_owner.team != self.team)
- self.monster_owner = world;
-
- // remove enemy that ran away
- if (self.enemy)
- if (self.delay <= time) // check if we can do the rescan now
- if (vlen(self.origin - self.enemy.origin) > autocvar_g_monster_zombie_target_range * self.scale)
- {
- //print("removing enemy, he is too far: ", ftos(vlen(self.origin - self.enemy.origin)), "\n");
- //print("delay was ", ftos(self.delay), "\n");
- self.enemy = world;
- }
- else
- self.delay = time + autocvar_g_monster_zombie_target_recheck_delay;
-
- // find an enemy if no enemy available
- if not(self.enemy)
- {
- self.enemy = FindTarget(self);
- if (self.enemy)
- self.delay = time + autocvar_g_monster_zombie_target_recheck_delay;
- }
-
- if (self.enemy)
- {
- // make sure zombie is angry
- self.state = ZOMBIE_STATE_ANGRY;
-
-
- // this zombie has an enemy, attack if close enough, go to it if not!
- traceline(self.origin, self.enemy.origin, FALSE, self);
- enemyDistance = vlen(trace_endpos - self.origin);
- mySpeed = vlen(self.velocity);
-
- //print("speed ", ftos(mySpeed), "\n");
-
- if (trace_ent == self.enemy)
- if (self.enemy.deadflag == DEAD_NO)
- if (mySpeed <= 30)
- if (enemyDistance <= autocvar_g_monster_zombie_attack_stand_range * self.scale)
- {
- //RadiusDamage (entity inflictor, entity attacker, float coredamage, float edgedamage, float rad, entity ignore, float forceintensity, float deathtype, entity directhitentity)
- zombie_attack_standing();
- finished = TRUE;
- }
- else if (enemyDistance <= autocvar_g_monster_zombie_attack_leap_range * self.scale)
- {
- // do attackleap (set yaw, velocity, and check do damage on the first player entity it touches)
- zombie_attack_leap();
- finished = TRUE;
- }
-
- }
-
- self.nextthink = time + 1;
+ self.nextthink = time + 0.3;
- if not(finished)
- {
- monster_move(autocvar_g_monster_zombie_speed_run, autocvar_g_monster_zombie_speed_walk, autocvar_g_monster_zombie_stopspeed, zombie_anim_runforward, zombie_anim_runforward, zombie_anim_idle);
-
- if (self.enemy || self.monster_owner)
- {
- self.nextthink = time + 0.1;
- return;
- }
- }
-
- if not(self.enemy || self.monster_owner || self.goalentity)
- {
- // stay idle
- //print("zombie is idling while waiting for some fresh meat...\n");
- self.frame = ((mySpeed <= 20) ? zombie_anim_idle : zombie_anim_runforward);
- self.nextthink = time + autocvar_g_monster_zombie_idle_timer * random();
- }
+ monster_move(autocvar_g_monster_zombie_speed_run, autocvar_g_monster_zombie_speed_walk, autocvar_g_monster_zombie_stopspeed, zombie_anim_runforward, zombie_anim_runforward, zombie_anim_idle);
}
void zombie_spawn()
self.classname = "monster_zombie";
self.nextthink = time + 2.1;
self.pain_finished = self.nextthink;
- self.state = ZOMBIE_STATE_SPAWNING;
self.frame = zombie_anim_spawn;
self.think = zombie_think;
self.sprite_height = 50 * self.scale;
+ self.checkattack = GenericCheckAttack;
+ self.attack_melee = zombie_attack_standing;
+ self.attack_ranged = zombie_attack_ranged;
self.skin = rint(random() * 4);
monster_hook_spawn(); // for post-spawn mods