From: Mario Date: Wed, 28 Aug 2013 16:26:43 +0000 (+1000) Subject: Begin cleaning up most monster functions (still a bit buggy) X-Git-Tag: xonotic-v0.8.0~241^2^2~157 X-Git-Url: https://git.rm.cloudns.org/?a=commitdiff_plain;h=7f46f66dc8e88333da6826603aa1f45d028e965c;p=xonotic%2Fxonotic-data.pk3dir.git Begin cleaning up most monster functions (still a bit buggy) --- diff --git a/qcsrc/client/Main.qc b/qcsrc/client/Main.qc index 6eef5cc0d..81d224faa 100644 --- a/qcsrc/client/Main.qc +++ b/qcsrc/client/Main.qc @@ -104,6 +104,7 @@ void CSQC_Init(void) // needs to be done so early because of the constants they create CALL_ACCUMULATED_FUNCTION(RegisterWeapons); + CALL_ACCUMULATED_FUNCTION(RegisterMonsters); CALL_ACCUMULATED_FUNCTION(RegisterGametypes); CALL_ACCUMULATED_FUNCTION(RegisterNotifications); CALL_ACCUMULATED_FUNCTION(RegisterDeathtypes); @@ -115,8 +116,7 @@ void CSQC_Init(void) precache_model("null"); precache_sound("misc/hit.wav"); precache_sound("misc/typehit.wav"); - - Monsters_Precache(); + generator_precache(); Projectile_Precache(); Hook_Precache(); diff --git a/qcsrc/client/monsters.qc b/qcsrc/client/monsters.qc index c3eef3c16..0dfbcf4c0 100644 --- a/qcsrc/client/monsters.qc +++ b/qcsrc/client/monsters.qc @@ -1,207 +1,3 @@ -string mid2info_model; -string mid2info_name; -vector mid2info_min; -vector mid2info_max; - -float monster_precached[MONSTER_LAST]; -void monster_mid2info(float _mid); - -void monster_precache(float _mid) -{ - monster_mid2info(_mid); - if(monster_precached[_mid]) - return; - - switch(_mid) - { - case MONSTER_ZOMBIE: - { - precache_model(ZOMBIE_MODEL); - break; - } - case MONSTER_BRUTE: - { - precache_model(BRUTE_MODEL); - break; - } - case MONSTER_ANIMUS: - { - precache_model(ANIMUS_MODEL); - break; - } - case MONSTER_SHAMBLER: - { - precache_model(SHAMBLER_MODEL); - break; - } - case MONSTER_BRUISER: - { - precache_model(BRUISER_MODEL); - break; - } - case MONSTER_WYVERN: - { - precache_model(WYVERN_MODEL); - break; - } - case MONSTER_CERBERUS: - { - precache_model(CERBERUS_MODEL); - break; - } - case MONSTER_SLIME: - { - precache_model(SLIME_MODEL); - precache_sound("weapons/rocket_impact.wav"); - break; - } - case MONSTER_KNIGHT: - { - precache_model(KNIGHT_MODEL); - break; - } - case MONSTER_STINGRAY: - { - precache_model(STINGRAY_MODEL); - break; - } - case MONSTER_MAGE: - { - precache_model(MAGE_MODEL); - break; - } - case MONSTER_SPIDER: - { - precache_model(SPIDER_MODEL); - - break; - } - } - - monster_precached[_mid] = TRUE; -} - -void Monsters_Precache() -{ - float i; - for(i = MONSTER_FIRST + 1; i < MONSTER_LAST; ++i) - monster_precache(i); -} - -void monster_mid2info(float _mid) -{ - switch(_mid) - { - case MONSTER_ZOMBIE: - { - mid2info_model = ZOMBIE_MODEL; - mid2info_name = "Zombie"; - mid2info_min = ZOMBIE_MIN; - mid2info_max = ZOMBIE_MAX; - break; - } - case MONSTER_BRUTE: - { - mid2info_model = BRUTE_MODEL; - mid2info_name = "Brute"; - mid2info_min = BRUTE_MIN; - mid2info_max = BRUTE_MAX; - break; - } - case MONSTER_ANIMUS: - { - mid2info_model = ANIMUS_MODEL; - mid2info_name = "Animus"; - mid2info_min = ANIMUS_MIN; - mid2info_max = ANIMUS_MAX; - if(self) self.scale = 1.3; - break; - } - case MONSTER_SHAMBLER: - { - mid2info_model = SHAMBLER_MODEL; - mid2info_name = "Shambler"; - mid2info_min = SHAMBLER_MIN; - mid2info_max = SHAMBLER_MAX; - if(self) self.scale = 1.3; - break; - } - case MONSTER_BRUISER: - { - mid2info_model = BRUISER_MODEL; - mid2info_name = "Bruiser"; - mid2info_min = BRUISER_MIN; - mid2info_max = BRUISER_MAX; - if(self) self.scale = 1.3; - break; - } - case MONSTER_WYVERN: - { - mid2info_model = WYVERN_MODEL; - mid2info_name = "Wyvern"; - mid2info_min = WYVERN_MIN; - mid2info_max = WYVERN_MAX; - if(self) self.scale = 1.3; - break; - } - case MONSTER_CERBERUS: - { - mid2info_model = CERBERUS_MODEL; - mid2info_name = "Cerberus"; - mid2info_min = CERBERUS_MIN; - mid2info_max = CERBERUS_MAX; - break; - } - case MONSTER_SLIME: - { - mid2info_model = SLIME_MODEL; - mid2info_name = "Slime"; - mid2info_min = SLIME_MIN; - mid2info_max = SLIME_MAX; - break; - } - case MONSTER_KNIGHT: - { - mid2info_model = KNIGHT_MODEL; - mid2info_name = "Knight"; - mid2info_min = KNIGHT_MIN; - mid2info_max = KNIGHT_MAX; - if(self) self.scale = 1.3; - break; - } - case MONSTER_STINGRAY: - { - mid2info_model = STINGRAY_MODEL; - mid2info_name = "Stingray"; - mid2info_min = STINGRAY_MIN; - mid2info_max = STINGRAY_MAX; - if(self) self.scale = 1.3; - break; - } - case MONSTER_MAGE: - { - mid2info_model = MAGE_MODEL; - mid2info_name = "Mage"; - mid2info_min = MAGE_MIN; - mid2info_max = MAGE_MAX; - break; - } - case MONSTER_SPIDER: - { - mid2info_model = SPIDER_MODEL; - mid2info_name = "Spider"; - mid2info_min = SPIDER_MIN; - mid2info_max = SPIDER_MAX; - break; - } - default: - { - dprint("WARNING: Unknown monster in CSQC\n"); - break; - } - } -} - .vector glowmod; void monster_changeteam() { @@ -216,8 +12,7 @@ void monster_changeteam() void monster_die() { - if(self.monsterid == MONSTER_SPIDER) - self.angles += '180 0 0'; + MON_ACTION(self.monsterid, MR_DEATH); self.solid = SOLID_CORPSE; } @@ -238,13 +33,21 @@ void monster_draw() } void monster_construct() -{ - monster_mid2info(self.monsterid); - self.netname = mid2info_name; +{ + vector min_s, max_s; + entity mon = get_monsterinfo(self.monsterid); + + min_s = mon.mins; + max_s = mon.maxs; + + if(mon.spawnflags & MONSTER_SIZE_BROKEN) + self.scale = 1.3; + + self.netname = M_NAME(self.monsterid); setorigin(self, self.origin); - setmodel(self, mid2info_model); - setsize(self, mid2info_min, mid2info_max); + setmodel(self, mon.model); + setsize(self, min_s, max_s); self.move_movetype = MOVETYPE_BOUNCE; self.health = 255; @@ -277,7 +80,6 @@ void ent_monster() self.skin = ReadByte(); self.team = ReadByte(); - monster_precache(self.monsterid); monster_construct(); monster_changeteam(); } diff --git a/qcsrc/client/monsters.qh b/qcsrc/client/monsters.qh index 83dac2956..aa6fb4126 100644 --- a/qcsrc/client/monsters.qh +++ b/qcsrc/client/monsters.qh @@ -1,2 +1 @@ void ent_monster(); -void Monsters_Precache(); diff --git a/qcsrc/client/progs.src b/qcsrc/client/progs.src index fd7003250..bc1ec35eb 100644 --- a/qcsrc/client/progs.src +++ b/qcsrc/client/progs.src @@ -27,6 +27,7 @@ Defs.qc ../common/command/shared_defs.qh ../common/urllib.qh ../common/animdecide.qh +../common/monsters/monsters.qh command/cl_cmd.qh autocvars.qh @@ -48,7 +49,7 @@ noise.qh tturrets.qh ../server/tturrets/include/turrets_early.qh monsters.qh -../server/monsters/lib/monsters_early.qh +../common/monsters/lib/monsters_early.qh ../server/movelib.qc ../server/generator.qh main.qh @@ -114,6 +115,7 @@ noise.qc ../server/w_all.qc ../common/explosion_equation.qc ../common/urllib.qc +../common/monsters/monsters.qc command/cl_cmd.qc ../warpzonelib/anglestransform.qc @@ -124,7 +126,6 @@ tturrets.qc ../server/generator.qc -../server/monsters/monsters.qh monsters.qc player_skeleton.qc diff --git a/qcsrc/common/monsters/all.qh b/qcsrc/common/monsters/all.qh new file mode 100644 index 000000000..0c4f0af99 --- /dev/null +++ b/qcsrc/common/monsters/all.qh @@ -0,0 +1,12 @@ +#include "monster/brute.qc" +#include "monster/animus.qc" +#include "monster/shambler.qc" +#include "monster/bruiser.qc" +#include "monster/wyvern.qc" +#include "monster/cerberus.qc" +#include "monster/slime.qc" +#include "monster/knight.qc" +#include "monster/stingray.qc" +#include "monster/mage.qc" +#include "monster/zombie.qc" +#include "monster/spider.qc" diff --git a/qcsrc/common/monsters/lib/defs.qh b/qcsrc/common/monsters/lib/defs.qh new file mode 100644 index 000000000..1f5fe1293 --- /dev/null +++ b/qcsrc/common/monsters/lib/defs.qh @@ -0,0 +1,51 @@ +.float(float attack_type) monster_attackfunc; +const float MONSTER_ATTACK_MELEE = 1; +const float MONSTER_ATTACK_RANGED = 2; + +.float candrop; + +.float attack_range; + +.float spawn_time; // stop monster from moving around right after spawning + +.string oldtarget2; +.float lastshielded; + +.vector oldangles; + +.float monster_respawned; // used to make sure we're not recounting respawned monster stats + +float monsters_spawned; + +const float MONSTERSKILL_NOTEASY = 256; // monster will not spawn on skill <= 2 +const float MONSTERSKILL_NOTMEDIUM = 512; // monster will not spawn on skill 3 +const float MONSTERSKILL_NOTHARD = 1024; // monster will not spawn on skill 4 +const float MONSTERSKILL_NOTINSANE = 2048; // monster will not spawn on skill 5 +const float MONSTERSKILL_NOTNIGHTMARE = 4096; // monster will not spawn on skill >= 6 + +// new flags +const float MONSTERFLAG_MINIBOSS = 1; // monster spawns as mini-boss (also has a chance of naturally becoming one) +const float MONSTERFLAG_APPEAR = 2; // delay spawn until triggered +const float MONSTERFLAG_NORESPAWN = 4; +const float MONSTERFLAG_SPAWNED = 512; // flag for spawned monsters + +.float msound_delay; // restricts some monster sounds +.string msound_idle; +.string msound_death; +.string msound_attack_melee; +.string msound_attack_ranged; +.string msound_spawn; +.string msound_sight; +.string msound_pain; + +.void() monster_spawnfunc; + +.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 + +const float MONSTER_STATE_ATTACK_LEAP = 1; +const float MONSTER_STATE_ATTACK_MELEE = 2; diff --git a/qcsrc/common/monsters/lib/monsters.qc b/qcsrc/common/monsters/lib/monsters.qc new file mode 100644 index 000000000..4850dc1e1 --- /dev/null +++ b/qcsrc/common/monsters/lib/monsters.qc @@ -0,0 +1,1096 @@ +// TODO: clean up this file? + +void() spawnfunc_item_minst_cells; + +void M_Item_Touch () +{ + if(self && IS_PLAYER(other) && other.deadflag == DEAD_NO) + { + Item_Touch(); + self.think = SUB_Remove; + self.nextthink = time + 0.1; + } +} + +void monster_item_spawn() +{ + if(self.monster_loot) + self.monster_loot(); + + self.gravity = 1; + self.velocity = randomvec() * 175 + '0 0 325'; + self.touch = M_Item_Touch; + + SUB_SetFade(self, time + autocvar_g_monsters_drop_time, 1); +} + +void Monster_DropItem (string itype, string itemsize) +{ + vector org = self.origin + ((self.mins + self.maxs) * 0.5); + entity e = spawn(); + + setorigin(e, org); + + switch(itype) + { + case "armor": + { + switch(itemsize) + { + case "mega": e.monster_loot = spawnfunc_item_armor_large; break; + case "large": e.monster_loot = spawnfunc_item_armor_big; break; + case "medium": e.monster_loot = spawnfunc_item_armor_medium; break; + case "small": e.monster_loot = spawnfunc_item_armor_small; break; + } + break; + } + case "health": + { + switch(itemsize) + { + case "mega": e.monster_loot = spawnfunc_item_health_mega; break; + case "large": e.monster_loot = spawnfunc_item_health_large; break; + case "medium": e.monster_loot = spawnfunc_item_health_medium; break; + case "small": e.monster_loot = spawnfunc_item_health_small; break; + } + break; + } + case "ammo": + { + switch(itemsize) + { + case "shells": e.monster_loot = spawnfunc_item_shells; break; + case "cells": e.monster_loot = spawnfunc_item_cells; break; + case "rockets": e.monster_loot = spawnfunc_item_rockets; break; + case "bullets": + case "nails": e.monster_loot = spawnfunc_item_bullets; break; + } + break; + } + } + + other = e; + MUTATOR_CALLHOOK(MonsterDropItem); + e = other; + + e.think = monster_item_spawn; + e.nextthink = time + 0.1; +} + +void monsters_setframe(float _frame) +{ + if(self.frame == _frame) + return; + + self.anim_start_time = time; + self.frame = _frame; + self.SendFlags |= MSF_ANIM; +} + +float monster_isvalidtarget (entity targ, entity ent) +{ + if(!targ || !ent) + return FALSE; // someone doesn't exist + + if(time < game_starttime) + return FALSE; // monsters do nothing before the match has started + + WarpZone_TraceLine(ent.origin, targ.origin, MOVE_NORMAL, ent); + + if(vlen(targ.origin - ent.origin) >= ent.target_range) + return FALSE; // enemy is too far away + + if not(targ.vehicle_flags & VHF_ISVEHICLE) + if(trace_ent != targ) + return FALSE; // we can't see the enemy + + if(targ.takedamage == DAMAGE_NO) + return FALSE; // enemy can't be damaged + + if(targ.items & IT_INVISIBILITY) + return FALSE; // enemy is invisible + + if(substring(targ.classname, 0, 10) == "onslaught_") + return FALSE; // don't attack onslaught targets + + if(IS_SPEC(targ) || IS_OBSERVER(targ)) + return FALSE; // enemy is a spectator + + if not(targ.vehicle_flags & VHF_ISVEHICLE) // vehicles dont count as alive? + if(targ.deadflag != DEAD_NO || ent.deadflag != DEAD_NO || targ.health <= 0 || ent.health <= 0) + return FALSE; // enemy/self is dead + + if(targ.monster_owner == ent || ent.monster_owner == targ) + return FALSE; // enemy owns us, or we own them + + if not(targ.vehicle_flags & VHF_ISVEHICLE) + if(targ.flags & FL_NOTARGET) + return FALSE; // enemy can't be targeted + + if not(autocvar_g_monsters_typefrag) + if(targ.BUTTON_CHAT) + return FALSE; // no typefragging! + + if not(IsDifferentTeam(targ, ent)) + return FALSE; // enemy is on our team + + if(autocvar_g_monsters_target_infront) + if(ent.enemy != targ) + { + float dot; + + makevectors (ent.angles); + dot = normalize (targ.origin - ent.origin) * v_forward; + + if(dot <= 0.3) + return FALSE; + } + + return TRUE; +} + +entity FindTarget (entity ent) +{ + if(MUTATOR_CALLHOOK(MonsterFindTarget)) { return ent.enemy; } // Handled by a mutator + entity e; + + for(e = world; (e = findflags(e, monster_attack, TRUE)); ) + if(monster_isvalidtarget(e, ent)) + return e; + + return world; +} + +void MonsterTouch () +{ + if(other == world) + return; + + if(self.enemy != other) + if not(other.flags & FL_MONSTER) + if(monster_isvalidtarget(other, self)) + self.enemy = other; +} + +void monster_sound(string msound, float sound_delay, float delaytoo) +{ + if(delaytoo && time < self.msound_delay) + return; // too early + + if(msound == "") + return; // sound doesn't exist + + sound(self, CHAN_AUTO, msound, VOL_BASE, ATTN_NORM); + + self.msound_delay = time + sound_delay; +} + +void monster_precachesounds(entity e) +{ + precache_sound(e.msound_idle); + precache_sound(e.msound_death); + precache_sound(e.msound_attack_melee); + precache_sound(e.msound_attack_ranged); + precache_sound(e.msound_sight); + precache_sound(e.msound_pain); +} + +void monster_setupsounds(string mon) +{ + if(self.msound_idle == "") self.msound_idle = strzone(strcat("monsters/", mon, "_idle.wav")); + if(self.msound_death == "") self.msound_death = strzone(strcat("monsters/", mon, "_death.wav")); + if(self.msound_pain == "") self.msound_pain = strzone(strcat("monsters/", mon, "_pain.wav")); + if(self.msound_attack_melee == "") self.msound_attack_melee = strzone(strcat("monsters/", mon, "_melee.wav")); + if(self.msound_attack_ranged == "") self.msound_attack_ranged = strzone(strcat("monsters/", mon, "_attack.wav")); + if(self.msound_sight == "") self.msound_sight = strzone(strcat("monsters/", mon, "_sight.wav")); +} + +float monster_melee (entity targ, float damg, float er, float deathtype, float dostop) +{ + float dot, rdmg = damg * random(); + + if (self.health <= 0) + return FALSE; + if (targ == world) + return FALSE; + + if(dostop) + { + self.velocity_x = 0; + self.velocity_y = 0; + self.state = MONSTER_STATE_ATTACK_MELEE; + self.SendFlags |= MSF_MOVE; + } + + makevectors (self.angles); + dot = normalize (targ.origin - self.origin) * v_forward; + + if(dot > er) + Damage(targ, self, self, rdmg * monster_skill, deathtype, targ.origin, normalize(targ.origin - self.origin)); + + return TRUE; +} + +void Monster_CheckDropCvars (string mon) +{ + if not(self.candrop) + return; // forced off + + string dropitem; + string dropsize; + + dropitem = cvar_string(strcat("g_monster_", mon, "_drop")); + dropsize = cvar_string(strcat("g_monster_", mon, "_drop_size")); + + if(autocvar_g_monsters_forcedrop) + Monster_DropItem(autocvar_g_monsters_drop_type, autocvar_g_monsters_drop_size); + else if(dropitem != "") + Monster_DropItem(dropitem, dropsize); + else + Monster_DropItem("armor", "medium"); +} + +void Monster_CheckMinibossFlag () +{ + if(MUTATOR_CALLHOOK(MonsterCheckBossFlag)) + return; + + float chance = random() * 100; + + // g_monsters_miniboss_chance cvar or spawnflags 64 causes a monster to be a miniboss + if ((self.spawnflags & MONSTERFLAG_MINIBOSS) || (chance < autocvar_g_monsters_miniboss_chance)) + { + self.health += autocvar_g_monsters_miniboss_healthboost; + self.flags |= MONSTERFLAG_MINIBOSS; + if not(self.weapon) + self.weapon = WEP_NEX; + } +} + +float Monster_CanRespawn(entity ent) +{ + other = ent; + if(MUTATOR_CALLHOOK(MonsterRespawn)) + return TRUE; // enabled by a mutator + + if(ent.spawnflags & MONSTERFLAG_NORESPAWN) + return FALSE; + + if not(autocvar_g_monsters_respawn) + return FALSE; + + return TRUE; +} + +void Monster_Fade () +{ + if(Monster_CanRespawn(self)) + { + self.monster_respawned = TRUE; + self.think = self.monster_spawnfunc; + self.nextthink = time + self.respawntime; + self.deadflag = DEAD_RESPAWNING; + if(self.spawnflags & MONSTER_RESPAWN_DEATHPOINT) + { + self.pos1 = self.origin; + self.pos2 = self.angles; + } + self.event_damage = func_null; + self.takedamage = DAMAGE_NO; + setorigin(self, self.pos1); + self.angles = self.pos2; + self.health = self.max_health; // TODO: check if resetting to max_health is wise here + + self.SendFlags |= MSF_MOVE; + self.SendFlags |= MSF_STATUS; + + return; + } + SUB_SetFade(self, time + 3, 1); +} + +float Monster_CanJump (vector vel) +{ + if(self.state) + return FALSE; // already attacking + if not(self.flags & FL_ONGROUND) + return FALSE; // not on the ground + if(self.health <= 0) + return FALSE; // called when dead? + if(time < self.attack_finished_single) + return FALSE; // still attacking + + vector old = self.velocity; + + self.velocity = vel; + tracetoss(self, self); + self.velocity = old; + if (trace_ent != self.enemy) + return FALSE; + + return TRUE; +} + +float monster_leap (float anm, void() touchfunc, vector vel, float anim_finished) +{ + if(!Monster_CanJump(vel)) + return FALSE; + + monsters_setframe(anm); + self.state = MONSTER_STATE_ATTACK_LEAP; + self.touch = touchfunc; + self.origin_z += 1; + self.velocity = vel; + self.flags &~= FL_ONGROUND; + + self.attack_finished_single = time + anim_finished; + + return TRUE; +} + +void monster_checkattack(entity e, entity targ) +{ + if(e == world) + return; + if(targ == world) + return; + + if not(e.monster_attackfunc) + return; + + if(time < e.attack_finished_single) + return; + + if(vlen(targ.origin - e.origin) <= e.attack_range) + if(e.monster_attackfunc(MONSTER_ATTACK_MELEE)) + { + monster_sound(e.msound_attack_melee, 0, FALSE); + return; + } + + if(e.monster_attackfunc(MONSTER_ATTACK_RANGED)) + { + monster_sound(e.msound_attack_ranged, 0, FALSE); + return; + } +} + +void monster_makevectors(entity e) +{ + vector v; + + v = CENTER_OR_VIEWOFS(e); + self.v_angle = vectoangles(v - (self.origin + self.view_ofs)); + self.v_angle_x = -self.v_angle_x; + + makevectors(self.v_angle); +} + +void monster_use () +{ + if (self.enemy) + return; + if (self.health <= 0) + return; + + if(!monster_isvalidtarget(activator, self)) + return; + + self.enemy = activator; +} + +float trace_path(vector from, vector to) +{ + vector dir = normalize(to - from) * 15, offset = '0 0 0'; + float trace1 = trace_fraction; + + offset_x = dir_y; + offset_y = -dir_x; + traceline (from+offset, to+offset, TRUE, self); + + traceline(from-offset, to-offset, TRUE, self); + + return ((trace1 < trace_fraction) ? trace1 : trace_fraction); +} + +.float last_trace; +.float last_enemycheck; // for checking enemy +vector monster_pickmovetarget(entity targ) +{ + // enemy is always preferred target + if(self.enemy) + { + self.monster_movestate = MONSTER_MOVE_ENEMY; + self.last_trace = time + 1.2; + return self.enemy.origin; + } + + switch(self.monster_moveflags) + { + case MONSTER_MOVE_OWNER: + { + self.monster_movestate = MONSTER_MOVE_OWNER; + self.last_trace = time + 0.3; + if(self.monster_owner && self.monster_owner.classname != "td_spawnpoint") + return self.monster_owner.origin; + } + case MONSTER_MOVE_SPAWNLOC: + { + self.monster_movestate = MONSTER_MOVE_SPAWNLOC; + self.last_trace = time + 2; + return self.pos1; + } + case MONSTER_MOVE_NOMOVE: + { + self.monster_movestate = MONSTER_MOVE_NOMOVE; + self.last_trace = time + 2; + return self.origin; + } + default: + case MONSTER_MOVE_WANDER: + { + vector pos; + self.monster_movestate = MONSTER_MOVE_WANDER; + self.last_trace = time + 2; + + self.angles_y = random() * 500; + makevectors(self.angles); + pos = self.origin + v_forward * 600; + + if(self.flags & FL_FLY || self.flags & FL_SWIM) + { + pos_z = random() * 200; + if(random() >= 0.5) + pos_z *= -1; + } + + if(targ) + { + self.last_trace = time + 0.5; + pos = targ.origin; + } + + return pos; + } + } +} + +void monster_move(float runspeed, float walkspeed, float stopspeed, float manim_run, float manim_walk, float manim_idle) +{ + fixedmakevectors(self.angles); + + if(self.target2) + self.goalentity = find(world, targetname, self.target2); + + entity targ; + + if(self.frozen) + { + self.revive_progress = bound(0, self.revive_progress + frametime * self.revive_speed, 1); + self.health = max(1, self.max_health * self.revive_progress); + + if(self.sprite) WaypointSprite_UpdateHealth(self.sprite, self.health); + + movelib_beak_simple(stopspeed); + + self.velocity = '0 0 0'; + self.enemy = world; + self.nextthink = time + 0.1; + + if(self.revive_progress >= 1) + Unfreeze(self); // wait for next think before attacking + + // don't bother updating angles here? + if(self.origin != self.oldorigin) + { + self.oldorigin = self.origin; + self.SendFlags |= MSF_MOVE; + } + + return; // no moving while frozen + } + + if(self.flags & FL_SWIM) + { + if(self.waterlevel < WATERLEVEL_WETFEET) + { + if(time >= self.last_trace) + { + self.last_trace = time + 0.4; + + Damage (self, world, world, 2, DEATH_DROWN, self.origin, '0 0 0'); + self.angles = '90 90 0'; + if(random() < 0.5) + { + self.velocity_y += random() * 50; + self.velocity_x -= random() * 50; + } + else + { + self.velocity_y -= random() * 50; + self.velocity_x += random() * 50; + } + self.velocity_z += random() * 150; + } + + + self.movetype = MOVETYPE_BOUNCE; + //self.velocity_z = -200; + + self.SendFlags |= MSF_MOVE | MSF_ANG; + + return; + } + else + { + self.angles = '0 0 0'; + self.movetype = MOVETYPE_WALK; + } + } + + targ = self.goalentity; + + monster_target = targ; + monster_speed_run = runspeed; + monster_speed_walk = walkspeed; + + if(MUTATOR_CALLHOOK(MonsterMove) || gameover || (round_handler_IsActive() && !round_handler_IsRoundStarted()) || time < game_starttime || (autocvar_g_campaign && !campaign_bots_may_start) || time < self.spawn_time) + { + runspeed = walkspeed = 0; + if(time >= self.spawn_time) + monsters_setframe(manim_idle); + movelib_beak_simple(stopspeed); + self.SendFlags |= MSF_MOVE; + return; + } + + targ = monster_target; + runspeed = monster_speed_run; + walkspeed = monster_speed_walk; + + if(IsDifferentTeam(self.monster_owner, self)) + self.monster_owner = world; + + if(time >= self.last_enemycheck) + { + if not(monster_isvalidtarget(self.enemy, self)) + self.enemy = world; + self.last_enemycheck = time + 2; + } + + if(self.enemy && self.enemy.health < 1) + self.enemy = world; // enough! + + if not(self.enemy) + { + self.enemy = FindTarget(self); + if(self.enemy) + monster_sound(self.msound_sight, 0, FALSE); + } + + if(self.state == MONSTER_STATE_ATTACK_MELEE && time >= self.attack_finished_single) + self.state = 0; + + if(self.state != MONSTER_STATE_ATTACK_MELEE) // don't move if set + if(time >= self.last_trace || self.enemy) // update enemy instantly + self.moveto = monster_pickmovetarget(targ); + + if not(self.enemy) + monster_sound(self.msound_idle, 5, TRUE); + + if(self.state != MONSTER_STATE_ATTACK_LEAP && self.state != MONSTER_STATE_ATTACK_MELEE) + self.steerto = steerlib_attract2(self.moveto, 0.5, 500, 0.95); + + if(self.state == MONSTER_STATE_ATTACK_LEAP && (self.flags & FL_ONGROUND)) + { + self.state = 0; + self.touch = MonsterTouch; + } + + //self.steerto = steerlib_attract2(self.moveto, 0.5, 500, 0.95); + + float turny = 0; + vector real_angle = vectoangles(self.steerto) - self.angles; + + if(self.state != MONSTER_STATE_ATTACK_LEAP && self.state != MONSTER_STATE_ATTACK_MELEE) + turny = 20; + + if(self.flags & FL_SWIM) + turny = vlen(self.angles - self.moveto); + + if(turny) + { + turny = bound(turny * -1, shortangle_f(real_angle_y, self.angles_y), turny); + self.angles_y += turny; + } + + if(self.state == MONSTER_STATE_ATTACK_MELEE) + self.moveto = self.origin; + else if(self.enemy) + self.moveto = self.moveto * 0.9 + ((self.origin + v_forward * 500) + randomvec() * 400) * 0.1; + + if not(self.flags & FL_FLY || self.flags & FL_SWIM) + self.moveto_z = self.origin_z; + + float l = vlen(self.moveto - self.origin); + float t1 = trace_path(self.origin+'0 0 10', self.moveto+'0 0 10'); + float t2 = trace_path(self.origin-'0 0 15', self.moveto-'0 0 15'); + + if(self.flags & FL_FLY || self.flags & FL_SWIM) + v_forward = normalize(self.moveto - self.origin); + + if(t1*l-t2*l>50 && (t1*l > 100 || t1 > 0.8)) + if(self.flags & FL_ONGROUND) + movelib_jump_simple(100); + + if(vlen(self.origin - self.moveto) > 64) + { + if(self.flags & FL_FLY || self.flags & FL_SWIM) + 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) + monsters_setframe((self.enemy) ? manim_run : manim_walk); + } + else + { + entity e = find(world, targetname, self.target2); + if(e.target2) + self.target2 = e.target2; + else if(e.target) + self.target2 = e.target; + + movelib_beak_simple(stopspeed); + if(time > self.attack_finished_single) + if(time > self.pain_finished) + if (vlen(self.velocity) <= 30) + monsters_setframe(manim_idle); + } + + monster_checkattack(self, self.enemy); + + if(self.angles != self.oldangles) + { + self.oldangles = self.angles; + self.SendFlags |= MSF_ANG; + } + + if(self.origin != self.oldorigin) + { + self.oldorigin = self.origin; + self.SendFlags |= MSF_MOVE; + } +} + +void monster_dead_think() +{ + self.think = monster_dead_think; + self.nextthink = time + 0.3; // don't need to update so often now + + self.deadflag = DEAD_DEAD; + + if(time >= self.ltime) + { + Monster_Fade(); + return; + } + + self.SendFlags |= MSF_MOVE; // keep up to date on the monster's location +} + +void monsters_setstatus() +{ + self.stat_monsters_total = monsters_total; + self.stat_monsters_killed = monsters_killed; +} + +void Monster_Appear() +{ + self.enemy = activator; + self.spawnflags &~= MONSTERFLAG_APPEAR; + self.monster_spawnfunc(); +} + +float Monster_CheckAppearFlags(entity ent) +{ + if not(ent.spawnflags & MONSTERFLAG_APPEAR) + return FALSE; + + ent.think = func_null; + ent.nextthink = 0; + ent.use = Monster_Appear; + ent.flags = FL_MONSTER; // set so this monster can get butchered + + return TRUE; +} + +void monsters_reset() +{ + setorigin(self, self.pos1); + self.angles = self.pos2; + + self.health = self.max_health; + self.velocity = '0 0 0'; + self.enemy = world; + self.goalentity = world; + self.attack_finished_single = 0; + self.moveto = self.origin; + + WaypointSprite_UpdateHealth(self.sprite, self.health); +} + +float monster_send(entity to, float sf) +{ + WriteByte(MSG_ENTITY, ENT_CLIENT_MONSTER); + WriteByte(MSG_ENTITY, sf); + if(sf & MSF_SETUP) + { + WriteByte(MSG_ENTITY, self.monsterid); + + WriteCoord(MSG_ENTITY, self.origin_x); + WriteCoord(MSG_ENTITY, self.origin_y); + WriteCoord(MSG_ENTITY, self.origin_z); + + WriteAngle(MSG_ENTITY, self.angles_x); + WriteAngle(MSG_ENTITY, self.angles_y); + + WriteByte(MSG_ENTITY, self.skin); + WriteByte(MSG_ENTITY, self.team); + } + + if(sf & MSF_ANG) + { + WriteShort(MSG_ENTITY, rint(self.angles_x)); + WriteShort(MSG_ENTITY, rint(self.angles_y)); + } + + if(sf & MSF_MOVE) + { + WriteShort(MSG_ENTITY, rint(self.origin_x)); + WriteShort(MSG_ENTITY, rint(self.origin_y)); + WriteShort(MSG_ENTITY, rint(self.origin_z)); + + WriteShort(MSG_ENTITY, rint(self.velocity_x)); + WriteShort(MSG_ENTITY, rint(self.velocity_y)); + WriteShort(MSG_ENTITY, rint(self.velocity_z)); + + WriteShort(MSG_ENTITY, rint(self.angles_y)); + } + + if(sf & MSF_ANIM) + { + WriteCoord(MSG_ENTITY, self.anim_start_time); + WriteByte(MSG_ENTITY, self.frame); + } + + if(sf & MSF_STATUS) + { + WriteByte(MSG_ENTITY, self.skin); + + WriteByte(MSG_ENTITY, self.team); + + WriteByte(MSG_ENTITY, self.deadflag); + + if(self.health <= 0) + WriteByte(MSG_ENTITY, 0); + else + WriteByte(MSG_ENTITY, ceil((self.health / self.max_health) * 255)); + } + + return TRUE; +} + +void monster_link(void() spawnproc) +{ + Net_LinkEntity(self, TRUE, 0, monster_send); + self.think = spawnproc; + self.nextthink = time; +} + +void monsters_corpse_damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) +{ + self.health -= damage; + + Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, self, attacker); + + if(self.health <= -100) // 100 health until gone? + { + Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, self, attacker); + + self.think = SUB_Remove; + self.nextthink = time + 0.1; + } +} + +void monster_die() +{ + self.think = monster_dead_think; + self.nextthink = self.ticrate; + self.ltime = time + 5; + + Monster_CheckDropCvars(self.netname); + + WaypointSprite_Kill(self.sprite); + + if(self.weaponentity) + { + remove(self.weaponentity); + self.weaponentity = world; + } + + monster_sound(self.msound_death, 0, FALSE); + + if(!(self.spawnflags & MONSTERFLAG_SPAWNED) && !self.monster_respawned) + monsters_killed += 1; + + if(self.candrop && self.weapon) + W_ThrowNewWeapon(self, self.weapon, 0, self.origin, randomvec() * 150 + '0 0 325'); + + if(IS_CLIENT(self.realowner)) + self.realowner.monstercount -= 1; + + self.event_damage = monsters_corpse_damage; + self.solid = SOLID_CORPSE; + self.takedamage = DAMAGE_AIM; + self.enemy = world; + self.movetype = MOVETYPE_TOSS; + self.moveto = self.origin; + self.touch = MonsterTouch; // reset incase monster was pouncing + + if not(self.flags & FL_FLY) + self.velocity = '0 0 0'; + + self.SendFlags |= MSF_MOVE; + + totalspawned -= 1; + + MON_ACTION(self.monsterid, MR_DEATH); +} + +void monsters_damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) +{ + if(self.frozen && deathtype != DEATH_KILL) + return; + + if(time < self.pain_finished && deathtype != DEATH_KILL) + return; + + if(time < self.spawnshieldtime) + return; + + if(deathtype != DEATH_KILL) + damage *= self.armorvalue; + + if(self.weaponentity && self.weaponentity.classname == "shield") + self.weaponentity.health -= damage; + + self.health -= damage; + + if(self.sprite) + WaypointSprite_UpdateHealth(self.sprite, self.health); + + self.dmg_time = time; + + if(sound_allowed(MSG_BROADCAST, attacker) && deathtype != DEATH_DROWN) + spamsound (self, CH_PAIN, "misc/bodyimpact1.wav", VOL_BASE, ATTN_NORM); // FIXME: PLACEHOLDER + + self.velocity += force * self.damageforcescale; + + if(deathtype != DEATH_DROWN) + { + 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(self.health <= 0) + { + if(self.sprite) + { + // Update one more time to avoid waypoint fading without emptying healthbar + WaypointSprite_UpdateHealth(self.sprite, 0); + } + + if(deathtype == DEATH_KILL) + self.candrop = FALSE; // killed by mobkill command + + // TODO: fix this? + activator = attacker; + other = self.enemy; + SUB_UseTargets(); + self.target2 = self.oldtarget2; // reset to original target on death, incase we respawn + + monster_die(); + + frag_attacker = attacker; + frag_target = self; + MUTATOR_CALLHOOK(MonsterDies); + + if(self.health <= -100) // check if we're already gibbed + { + Violence_GibSplash(self, 1, 0.5, attacker); + + self.think = SUB_Remove; + self.nextthink = time + 0.1; + } + } + + self.SendFlags |= MSF_STATUS; +} + +void monster_think() +{ + self.think = monster_think; + self.nextthink = self.ticrate; + + MON_ACTION(self.monsterid, MR_THINK); +} + +void monster_spawn() +{ + MON_ACTION(self.monsterid, MR_SETUP); + + if not(self.monster_respawned) + Monster_CheckMinibossFlag(); + + self.max_health = self.health; + self.pain_finished = self.nextthink; + self.anim_start_time = time; + + if not(self.noalign) + { + setorigin(self, self.origin + '0 0 20'); + tracebox(self.origin + '0 0 100', self.mins, self.maxs, self.origin - '0 0 10000', MOVE_WORLDONLY, self); + setorigin(self, trace_endpos); + } + + if not(self.monster_respawned) + if not(self.skin) + self.skin = rint(random() * 4); + + self.pos1 = self.origin; + + monster_setupsounds(self.netname); + + monster_precachesounds(self); + + if(teamplay) + self.monster_attack = TRUE; // we can have monster enemies in team games + + if(autocvar_g_monsters_healthbars) + { + WaypointSprite_Spawn(strzone(strdecolorize(self.monster_name)), 0, 600, self, '0 0 1' * (self.maxs_z + 15), world, 0, self, sprite, FALSE, RADARICON_DANGER, ((self.team) ? Team_ColorRGB(self.team) : '1 0 0')); + WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health); + WaypointSprite_UpdateHealth(self.sprite, self.health); + } + + monster_sound(self.msound_spawn, 0, FALSE); + + MUTATOR_CALLHOOK(MonsterSpawn); + + self.think = monster_think; + self.nextthink = time + self.ticrate; + + self.SendFlags = MSF_SETUP; +} + +float monster_initialize(float mon_id, float nodrop) +{ + if not(autocvar_g_monsters) + return FALSE; + + vector min_s, max_s; + entity mon = get_monsterinfo(mon_id); + + // support for quake style removing monsters based on skill + if(monster_skill <= autocvar_g_monsters_skill_easy && (self.spawnflags & MONSTERSKILL_NOTEASY)) { return FALSE; } + if(monster_skill == autocvar_g_monsters_skill_normal && (self.spawnflags & MONSTERSKILL_NOTMEDIUM)) { return FALSE; } + if(monster_skill == autocvar_g_monsters_skill_hard && (self.spawnflags & MONSTERSKILL_NOTHARD)) { return FALSE; } + if(monster_skill == autocvar_g_monsters_skill_insane && (self.spawnflags & MONSTERSKILL_NOTINSANE)) { return FALSE; } + if(monster_skill >= autocvar_g_monsters_skill_nightmare && (self.spawnflags & MONSTERSKILL_NOTNIGHTMARE)) { return FALSE; } + + if(self.monster_name == "") + self.monster_name = M_NAME(mon_id); + + if(self.team && !teamplay) + self.team = 0; + + self.flags = FL_MONSTER; + + if not(self.spawnflags & MONSTERFLAG_SPAWNED) // naturally spawned monster + if not(self.monster_respawned) + monsters_total += 1; + + min_s = mon.mins; + max_s = mon.maxs; + + self.netname = mon.netname; + + setsize(self, min_s, max_s); + self.takedamage = DAMAGE_AIM; + self.bot_attack = TRUE; + self.iscreature = TRUE; + self.teleportable = TRUE; + self.damagedbycontents = TRUE; + self.monsterid = mon_id; + self.damageforcescale = 0; + self.event_damage = monsters_damage; + self.touch = MonsterTouch; + self.use = monster_use; + self.solid = SOLID_BBOX; + self.movetype = MOVETYPE_WALK; + self.spawnshieldtime = time + autocvar_g_monsters_spawnshieldtime; + monsters_spawned += 1; + self.enemy = world; + self.velocity = '0 0 0'; + self.moveto = self.origin; + self.pos2 = self.angles; + self.reset = monsters_reset; + self.candrop = TRUE; + self.view_ofs = '0 0 1' * (self.maxs_z * 0.5); + self.oldtarget2 = self.target2; + self.deadflag = DEAD_NO; + self.noalign = nodrop; + self.spawn_time = time; + self.gravity = 1; + self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_BOTCLIP | DPCONTENTS_MONSTERCLIP; + + if(mon.spawnflags & MONSTER_TYPE_SWIM) + self.flags |= FL_SWIM; + + if(mon.spawnflags & MONSTER_TYPE_FLY) + { + self.flags |= FL_FLY; + self.movetype = MOVETYPE_FLY; + } + + if not(self.scale) + self.scale = 1; + + if not(self.attack_range) + self.attack_range = 120; + + if not(self.ticrate) + self.ticrate = autocvar_g_monsters_think_delay; + + self.ticrate = bound(sys_frametime, self.ticrate, 60); + + if not(self.armorvalue) + self.armorvalue = 1; // multiplier + + if not(self.target_range) + self.target_range = autocvar_g_monsters_target_range; + + if not(self.respawntime) + self.respawntime = autocvar_g_monsters_respawn_delay; + + if not(self.monster_moveflags) + self.monster_moveflags = MONSTER_MOVE_WANDER; + + monster_link(monster_spawn); + + return TRUE; +} diff --git a/qcsrc/common/monsters/lib/monsters_early.qh b/qcsrc/common/monsters/lib/monsters_early.qh new file mode 100644 index 000000000..f690cdc59 --- /dev/null +++ b/qcsrc/common/monsters/lib/monsters_early.qh @@ -0,0 +1,35 @@ +// for definitions used outside the monsters folder + +#ifdef SVQC +.string spawnmob; +.float monster_attack; + +float monster_skill; +float spawncode_first_load; // used to tell the player the monster database is loading (TODO: fix this?) + +.entity monster_owner; // new monster owner entity, fixes non-solid monsters +.float monstercount; // per player monster count + +.float stat_monsters_killed; // stats +.float stat_monsters_total; +float monsters_total; +float monsters_killed; +void monsters_setstatus(); // monsters.qc +.float monster_moveflags; // checks where to move when not attacking + +#endif // SVQC + +#ifndef MENUQC + +.float anim_start_time; + +float MSF_UPDATE = 2; +float MSF_STATUS = 4; +float MSF_SETUP = 8; +float MSF_ANG = 16; +float MSF_MOVE = 32; +float MSF_ANIM = 64; + +float MSF_FULL_UPDATE = 16777215; + +#endif // CSQC/SVQC diff --git a/qcsrc/common/monsters/lib/spawn.qc b/qcsrc/common/monsters/lib/spawn.qc new file mode 100644 index 000000000..95e0261ae --- /dev/null +++ b/qcsrc/common/monsters/lib/spawn.qc @@ -0,0 +1,65 @@ +entity spawnmonster (string monster, float mnster, entity spawnedby, entity own, vector orig, float respwn, float moveflag) +{ + if(!spawncode_first_load) + { + initialize_field_db(); + spawncode_first_load = TRUE; + } + + entity e = spawn(); + + e.spawnflags = MONSTERFLAG_SPAWNED; + + if not(respwn) + e.spawnflags |= MONSTERFLAG_NORESPAWN; + + setorigin(e, orig); + + if(monster != "") + { + float i, found = 0; + entity mon; + for(i = MON_FIRST; i <= MON_LAST; ++i) + { + mon = get_monsterinfo(i); + if(mon.netname == monster) + { + found = TRUE; + break; + } + } + if not(found) + monster = (get_monsterinfo(MON_FIRST)).netname; + } + + if(monster == "") + if(mnster) + monster = (get_monsterinfo(mnster)).netname; + + e.realowner = spawnedby; + + if(moveflag) + e.monster_moveflags = moveflag; + + if(IS_PLAYER(spawnedby)) + { + if(teamplay && autocvar_g_monsters_teams) + e.team = spawnedby.team; // colors handled in spawn code + + if(e.team) + e.colormap = 1024; + else + e.colormap = spawnedby.colormap; + + if(autocvar_g_monsters_owners) + e.monster_owner = own; // using .owner makes the monster non-solid for its master + + e.angles = spawnedby.angles; + } + + monster = strcat("$ spawnfunc_monster_", monster); + + target_spawn_edit_entity(e, monster, world, world, world, world, world); + + return e; +} diff --git a/qcsrc/common/monsters/monster/animus.qc b/qcsrc/common/monsters/monster/animus.qc new file mode 100644 index 000000000..bd7639c09 --- /dev/null +++ b/qcsrc/common/monsters/monster/animus.qc @@ -0,0 +1,143 @@ +#ifdef REGISTER_MONSTER +REGISTER_MONSTER( +/* MON_##id */ ANIMUS, +/* function */ m_animus, +/* spawnflags */ MONSTER_SIZE_BROKEN, +/* mins,maxs */ '-41 -41 -31', '41 41 31', +/* model */ "demon.mdl", +/* netname */ "animus", +/* fullname */ _("Animus") +); + +#else +#ifdef SVQC +float autocvar_g_monster_animus; +float autocvar_g_monster_animus_health; +float autocvar_g_monster_animus_attack_jump_damage; +float autocvar_g_monster_animus_damage; +float autocvar_g_monster_animus_speed_walk; +float autocvar_g_monster_animus_speed_run; + +const float animus_anim_stand = 0; +const float animus_anim_walk = 1; +const float animus_anim_run = 2; +const float animus_anim_leap = 3; +const float animus_anim_pain = 4; +const float animus_anim_death = 5; +const float animus_anim_attack = 6; + +void animus_touch_jump() +{ + if (self.health <= 0) + return; + + if (monster_isvalidtarget(other, self)) + { + if (vlen(self.velocity) > 300) + { + Damage(other, self, self, autocvar_g_monster_animus_attack_jump_damage * monster_skill, DEATH_MONSTER_ANIMUS, other.origin, normalize(other.origin - self.origin)); + self.touch = MonsterTouch; // instantly turn it off to stop damage spam + } + } + + if(trace_dphitcontents) + self.touch = MonsterTouch; +} + +float animus_attack(float attack_type) +{ + switch(attack_type) + { + case MONSTER_ATTACK_MELEE: + { + monsters_setframe(animus_anim_attack); + self.attack_finished_single = time + 1; + monster_melee(self.enemy, autocvar_g_monster_animus_damage, 0.3, DEATH_MONSTER_ANIMUS, TRUE); + + return TRUE; + } + case MONSTER_ATTACK_RANGED: + { + makevectors(self.angles); + if(monster_leap(animus_anim_leap, animus_touch_jump, v_forward * 700 + '0 0 300', 0.8)) + return TRUE; + } + } + + return FALSE; +} + +void spawnfunc_monster_animus() +{ + if not(autocvar_g_monster_animus) { remove(self); return; } + + self.classname = "monster_animus"; + + self.monster_spawnfunc = spawnfunc_monster_animus; + + if(Monster_CheckAppearFlags(self)) + return; + + if not(monster_initialize(MON_ANIMUS, FALSE)) { remove(self); return; } +} + +// compatibility with old spawns +void spawnfunc_monster_demon1() { spawnfunc_monster_animus(); } +void spawnfunc_monster_demon() { spawnfunc_monster_animus(); } + +float m_animus(float req) +{ + switch(req) + { + case MR_THINK: + { + monster_move(autocvar_g_monster_animus_speed_run, autocvar_g_monster_animus_speed_walk, 100, animus_anim_run, animus_anim_walk, animus_anim_stand); + return TRUE; + } + case MR_DEATH: + { + monsters_setframe(animus_anim_death); + return TRUE; + } + case MR_SETUP: + { + if not(self.health) self.health = autocvar_g_monster_animus_health; + + self.monster_attackfunc = animus_attack; + monsters_setframe(animus_anim_stand); + + return TRUE; + } + case MR_INIT: + { + // nothing + return TRUE; + } + } + + return TRUE; +} + +#endif // SVQC +#ifdef CSQC +float m_animus(float req) +{ + switch(req) + { + case MR_DEATH: + { + // nothing + return TRUE; + } + case MR_INIT: + { + precache_model ("models/monsters/demon.mdl"); + return TRUE; + } + } + + return TRUE; +} + +#endif // CSQC +#endif // REGISTER_MONSTER diff --git a/qcsrc/common/monsters/monster/bruiser.qc b/qcsrc/common/monsters/monster/bruiser.qc new file mode 100644 index 000000000..fe16f9216 --- /dev/null +++ b/qcsrc/common/monsters/monster/bruiser.qc @@ -0,0 +1,125 @@ +#ifdef REGISTER_MONSTER +REGISTER_MONSTER( +/* MON_##id */ BRUISER, +/* function */ m_bruiser, +/* spawnflags */ MONSTER_SIZE_BROKEN, +/* mins,maxs */ '-20 -20 -31', '20 20 53', +/* model */ "knight.mdl", +/* netname */ "bruiser", +/* fullname */ _("Bruiser") +); + +#else +#ifdef SVQC +float autocvar_g_monster_bruiser; +float autocvar_g_monster_bruiser_health; +float autocvar_g_monster_bruiser_melee_damage; +float autocvar_g_monster_bruiser_speed_walk; +float autocvar_g_monster_bruiser_speed_run; + +const float bruiser_anim_stand = 0; +const float bruiser_anim_run = 1; +const float bruiser_anim_runattack = 2; +const float bruiser_anim_pain1 = 3; +const float bruiser_anim_pain2 = 4; +const float bruiser_anim_attack = 5; +const float bruiser_anim_walk = 6; +const float bruiser_anim_kneel = 7; +const float bruiser_anim_standing = 8; +const float bruiser_anim_death1 = 9; +const float bruiser_anim_death2 = 10; + +float bruiser_attack(float attack_type) +{ + switch(attack_type) + { + case MONSTER_ATTACK_MELEE: + { + float len = vlen(self.velocity); + monsters_setframe((len < 50) ? bruiser_anim_attack : bruiser_anim_runattack); + self.attack_finished_single = time + 1.25; + + monster_melee(self.enemy, autocvar_g_monster_bruiser_melee_damage, 0.3, DEATH_MONSTER_BRUISER, FALSE); + + return TRUE; + } + case MONSTER_ATTACK_RANGED: + { + // no ranged attacks for bruiser + return FALSE; + } + } + + return FALSE; +} + +void spawnfunc_monster_bruiser() +{ + if not(autocvar_g_monster_bruiser) { remove(self); return; } + + self.classname = "monster_bruiser"; + + self.monster_spawnfunc = spawnfunc_monster_bruiser; + + if(Monster_CheckAppearFlags(self)) + return; + + if not(monster_initialize(MON_BRUISER, FALSE)) { remove(self); return; } +} + +float m_bruiser(float req) +{ + switch(req) + { + case MR_THINK: + { + monster_move(autocvar_g_monster_bruiser_speed_run, autocvar_g_monster_bruiser_speed_walk, 50, bruiser_anim_run, bruiser_anim_walk, bruiser_anim_stand); + return TRUE; + } + case MR_DEATH: + { + monsters_setframe((random() > 0.5) ? bruiser_anim_death1 : bruiser_anim_death2); + return TRUE; + } + case MR_SETUP: + { + if not(self.health) self.health = autocvar_g_monster_bruiser_health; + + self.monster_attackfunc = bruiser_attack; + monsters_setframe(bruiser_anim_stand); + + return TRUE; + } + case MR_INIT: + { + // nothing + return TRUE; + } + } + + return TRUE; +} + +#endif // SVQC +#ifdef CSQC +float m_bruiser(float req) +{ + switch(req) + { + case MR_DEATH: + { + // nothing + return TRUE; + } + case MR_INIT: + { + precache_model ("models/monsters/knight.mdl"); + return TRUE; + } + } + + return TRUE; +} + +#endif // CSQC +#endif // REGISTER_MONSTER diff --git a/qcsrc/common/monsters/monster/brute.qc b/qcsrc/common/monsters/monster/brute.qc new file mode 100644 index 000000000..226e0d588 --- /dev/null +++ b/qcsrc/common/monsters/monster/brute.qc @@ -0,0 +1,255 @@ +#ifdef REGISTER_MONSTER +REGISTER_MONSTER( +/* MON_##id */ BRUTE, +/* function */ m_brute, +/* spawnflags */ 0, +/* mins,maxs */ '-36 -36 -20', '36 36 50', +/* model */ "ogre.dpm", +/* netname */ "brute", +/* fullname */ _("Brute") +); + +#else +#ifdef SVQC +float autocvar_g_monster_brute; +float autocvar_g_monster_brute_health; +float autocvar_g_monster_brute_chainsaw_damage; +float autocvar_g_monster_brute_speed_walk; +float autocvar_g_monster_brute_speed_run; +float autocvar_g_monster_brute_attack_uzi_bullets; +float autocvar_g_monster_brute_attack_uzi_damage; +float autocvar_g_monster_brute_attack_uzi_force; +float autocvar_g_monster_brute_attack_uzi_chance; +float autocvar_g_monster_brute_attack_grenade_damage; +float autocvar_g_monster_brute_attack_grenade_edgedamage; +float autocvar_g_monster_brute_attack_grenade_force; +float autocvar_g_monster_brute_attack_grenade_radius; + +const float brute_anim_idle = 0; +const float brute_anim_walk = 1; +const float brute_anim_run = 2; +const float brute_anim_pain = 3; +const float brute_anim_swing = 4; +const float brute_anim_die = 5; + +.float brute_cycles; + +void brute_blade() +{ + self.brute_cycles += 1; + self.angles_y = self.angles_y + random()* 25; + + monster_melee(self.enemy, autocvar_g_monster_brute_chainsaw_damage, 0.3, DEATH_MONSTER_BRUTE_BLADE, TRUE); + + if(self.brute_cycles <= 4) + defer(0.2, brute_blade); +} + +void brute_uzi() +{ + self.brute_cycles += 1; + + monster_makevectors(self.enemy); + + W_SetupShot (self, autocvar_g_antilag_bullets && 18000 >= autocvar_g_antilag_bullets, 0, "weapons/uzi_fire.wav", CH_WEAPON_A, autocvar_g_monster_brute_attack_uzi_damage); + fireBallisticBullet(w_shotorg, w_shotdir, 0.02, 18000, 5, autocvar_g_monster_brute_attack_uzi_damage, autocvar_g_monster_brute_attack_uzi_force, DEATH_MONSTER_BRUTE_UZI, 0, 1, 115); + endFireBallisticBullet(); + + if(self.brute_cycles <= autocvar_g_monster_brute_attack_uzi_bullets) + defer(0.1, brute_uzi); +} + +void brute_grenade_explode() +{ + pointparticles(particleeffectnum("grenade_explode"), self.origin, '0 0 0', 1); + sound(self, CH_SHOTS, "weapons/grenade_impact.wav", VOL_BASE, ATTN_NORM); + + self.event_damage = func_null; + self.takedamage = DAMAGE_NO; + + if(self.movetype == MOVETYPE_NONE) + self.velocity = self.oldvelocity; + + RadiusDamage (self, self.realowner, autocvar_g_monster_brute_attack_grenade_damage, autocvar_g_monster_brute_attack_grenade_edgedamage, autocvar_g_monster_brute_attack_grenade_radius, world, autocvar_g_monster_brute_attack_grenade_force, self.projectiledeathtype, other); + + remove (self); +} + +void brute_grenade_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) +{ + if (self.health <= 0) + return; + + if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions + return; // g_projectiles_damage says to halt + + self.health = self.health - damage; + + if (self.health <= 0) + W_PrepareExplosionByDamage(attacker, self.use); +} + +void brute_grenade_touch() +{ + PROJECTILE_TOUCH; + + self.use (); +} + +void brute_grenade_think() +{ + self.nextthink = time; + if (time > self.cnt) + { + other = world; + brute_grenade_explode(); + return; + } +} + +void brute_grenade() +{ + entity gren; + + W_SetupShot_ProjectileSize (self, '-3 -3 -3', '3 3 3', FALSE, 4, "weapons/grenade_fire.wav", CH_WEAPON_A, autocvar_g_monster_brute_attack_grenade_damage); + w_shotdir = v_forward; // no TrueAim for grenades please + + gren = spawn (); + gren.owner = gren.realowner = self; + gren.classname = "grenade"; + gren.bot_dodge = TRUE; + gren.bot_dodgerating = autocvar_g_monster_brute_attack_grenade_damage; + gren.movetype = MOVETYPE_BOUNCE; + PROJECTILE_MAKETRIGGER(gren); + gren.projectiledeathtype = DEATH_MONSTER_BRUTE_GRENADE; + setorigin(gren, w_shotorg); + setsize(gren, '-3 -3 -3', '3 3 3'); + + gren.cnt = time + 5; + gren.nextthink = time; + gren.think = brute_grenade_think; + gren.use = brute_grenade_explode; + gren.touch = brute_grenade_touch; + + gren.takedamage = DAMAGE_YES; + gren.health = autocvar_g_balance_grenadelauncher_primary_health; + gren.damageforcescale = autocvar_g_balance_grenadelauncher_primary_damageforcescale; + gren.event_damage = brute_grenade_damage; + gren.damagedbycontents = TRUE; + gren.missile_flags = MIF_SPLASH | MIF_ARC; + W_SETUPPROJECTILEVELOCITY_UP(gren, g_balance_grenadelauncher_primary); + + gren.angles = vectoangles (gren.velocity); + gren.flags = FL_PROJECTILE; + + CSQCProjectile(gren, TRUE, PROJECTILE_GRENADE, TRUE); +} + +float brute_attack(float attack_type) +{ + switch(attack_type) + { + case MONSTER_ATTACK_MELEE: + { + self.brute_cycles = 0; + monsters_setframe(brute_anim_swing); + self.attack_finished_single = time + 1.3; + brute_blade(); + + return TRUE; + } + case MONSTER_ATTACK_RANGED: + { + self.brute_cycles = 0; + if(random() <= autocvar_g_monster_brute_attack_uzi_chance) + { + monsters_setframe(brute_anim_pain); + self.attack_finished_single = time + 0.8; + defer(0.1, brute_uzi); + } + else + { + monster_makevectors(self.enemy); + brute_grenade(); + monsters_setframe(brute_anim_pain); + self.attack_finished_single = time + 1.2; + } + + return TRUE; + } + } + + return FALSE; +} + +void spawnfunc_monster_brute() +{ + if not(autocvar_g_monster_brute) { remove(self); return; } + + self.classname = "monster_brute"; + + self.monster_spawnfunc = spawnfunc_monster_brute; + + if(Monster_CheckAppearFlags(self)) + return; + + if not(monster_initialize(MON_BRUTE, FALSE)) { remove(self); return; } +} + +float m_brute(float req) +{ + switch(req) + { + case MR_THINK: + { + monster_move(autocvar_g_monster_brute_speed_run, autocvar_g_monster_brute_speed_walk, 300, brute_anim_run, brute_anim_walk, brute_anim_idle); + return TRUE; + } + case MR_DEATH: + { + monsters_setframe(brute_anim_die); + return TRUE; + } + case MR_SETUP: + { + if not(self.health) self.health = autocvar_g_monster_brute_health; + + self.monster_attackfunc = brute_attack; + monsters_setframe(brute_anim_idle); + self.weapon = WEP_GRENADE_LAUNCHER; + + return TRUE; + } + case MR_INIT: + { + // nothing + return TRUE; + } + } + + return TRUE; +} + +#endif // SVQC +#ifdef CSQC +float m_brute(float req) +{ + switch(req) + { + case MR_DEATH: + { + // nothing + return TRUE; + } + case MR_INIT: + { + precache_model ("models/monsters/ogre.dpm"); + return TRUE; + } + } + + return TRUE; +} + +#endif // CSQC +#endif // REGISTER_MONSTER diff --git a/qcsrc/common/monsters/monster/cerberus.qc b/qcsrc/common/monsters/monster/cerberus.qc new file mode 100644 index 000000000..907c07ffb --- /dev/null +++ b/qcsrc/common/monsters/monster/cerberus.qc @@ -0,0 +1,136 @@ +#ifdef REGISTER_MONSTER +REGISTER_MONSTER( +/* MON_##id */ CERBERUS, +/* function */ m_cerberus, +/* spawnflags */ 0, +/* mins,maxs */ '-16 -16 -24', '16 16 12', +/* model */ "dog.dpm", +/* netname */ "cerberus", +/* fullname */ _("Cerberus") +); + +#else +#ifdef SVQC +float autocvar_g_monster_cerberus; +float autocvar_g_monster_cerberus_health; +float autocvar_g_monster_cerberus_bite_damage; +float autocvar_g_monster_cerberus_attack_jump_damage; +float autocvar_g_monster_cerberus_speed_walk; +float autocvar_g_monster_cerberus_speed_run; + +const float cerberus_anim_idle = 0; +const float cerberus_anim_walk = 1; +const float cerberus_anim_run = 2; +const float cerberus_anim_attack = 3; +const float cerberus_anim_die = 4; +const float cerberus_anim_pain = 5; + +void cerberus_touch_jump() +{ + if (other.takedamage) + if (vlen(self.velocity) > 300) + { + Damage(self.enemy, self, self, autocvar_g_monster_cerberus_attack_jump_damage * monster_skill, DEATH_MONSTER_CERBERUS_JUMP, self.enemy.origin, normalize(self.enemy.origin - self.origin)); + self.touch = MonsterTouch; + } + + if(trace_dphitcontents) + self.touch = MonsterTouch; +} + +float cerberus_attack(float attack_type) +{ + switch(attack_type) + { + case MONSTER_ATTACK_MELEE: + { + monsters_setframe(cerberus_anim_attack); + self.attack_finished_single = time + 0.7; + monster_melee(self.enemy, autocvar_g_monster_cerberus_bite_damage, 0.2, DEATH_MONSTER_CERBERUS_BITE, TRUE); + + return TRUE; + } + case MONSTER_ATTACK_RANGED: + { + makevectors(self.angles); + if(monster_leap(cerberus_anim_attack, cerberus_touch_jump, v_forward * 300 + '0 0 200', 0.8)) + return TRUE; + } + } + + return FALSE; +} + +void spawnfunc_monster_cerberus() +{ + if not(autocvar_g_monster_cerberus) { remove(self); return; } + + self.classname = "monster_cerberus"; + + self.monster_spawnfunc = spawnfunc_monster_cerberus; + + if(Monster_CheckAppearFlags(self)) + return; + + if not(monster_initialize(MON_CERBERUS, FALSE)) { remove(self); return; } +} + +// compatibility with old spawns +void spawnfunc_monster_dog() { spawnfunc_monster_cerberus(); } + +float m_cerberus(float req) +{ + switch(req) + { + case MR_THINK: + { + monster_move(autocvar_g_monster_cerberus_speed_run, autocvar_g_monster_cerberus_speed_walk, 50, cerberus_anim_run, cerberus_anim_walk, cerberus_anim_idle); + return TRUE; + } + case MR_DEATH: + { + monsters_setframe(cerberus_anim_die); + return TRUE; + } + case MR_SETUP: + { + if not(self.health) self.health = autocvar_g_monster_cerberus_health; + + self.monster_attackfunc = cerberus_attack; + monsters_setframe(cerberus_anim_idle); + + return TRUE; + } + case MR_INIT: + { + // nothing + return TRUE; + } + } + + return TRUE; +} + +#endif // SVQC +#ifdef CSQC +float m_cerberus(float req) +{ + switch(req) + { + case MR_DEATH: + { + // nothing + return TRUE; + } + case MR_INIT: + { + precache_model ("models/monsters/dog.dpm"); + return TRUE; + } + } + + return TRUE; +} + +#endif // CSQC +#endif // REGISTER_MONSTER \ No newline at end of file diff --git a/qcsrc/common/monsters/monster/knight.qc b/qcsrc/common/monsters/monster/knight.qc new file mode 100644 index 000000000..bc2a0e950 --- /dev/null +++ b/qcsrc/common/monsters/monster/knight.qc @@ -0,0 +1,334 @@ +#ifdef REGISTER_MONSTER +REGISTER_MONSTER( +/* MON_##id */ KNIGHT, +/* function */ m_knight, +/* spawnflags */ MONSTER_SIZE_BROKEN, +/* mins,maxs */ '-20 -20 -32', '20 20 41', +/* model */ "hknight.mdl", +/* netname */ "knight", +/* fullname */ _("Knight") +); + +#else +#ifdef SVQC +float autocvar_g_monster_knight; +float autocvar_g_monster_knight_health; +float autocvar_g_monster_knight_melee_damage; +float autocvar_g_monster_knight_inferno_damage; +float autocvar_g_monster_knight_inferno_damagetime; +float autocvar_g_monster_knight_inferno_chance; +float autocvar_g_monster_knight_speed_walk; +float autocvar_g_monster_knight_speed_run; +float autocvar_g_monster_knight_fireball_damage; +float autocvar_g_monster_knight_fireball_force; +float autocvar_g_monster_knight_fireball_radius; +float autocvar_g_monster_knight_fireball_chance; +float autocvar_g_monster_knight_fireball_edgedamage; +float autocvar_g_monster_knight_spike_chance; +float autocvar_g_monster_knight_spike_force; +float autocvar_g_monster_knight_spike_radius; +float autocvar_g_monster_knight_spike_edgedamage; +float autocvar_g_monster_knight_spike_damage; +float autocvar_g_monster_knight_jump_chance; +float autocvar_g_monster_knight_jump_damage; +float autocvar_g_monster_knight_jump_dist; + +const float knight_anim_stand = 0; +const float knight_anim_walk = 1; +const float knight_anim_run = 2; +const float knight_anim_pain = 3; +const float knight_anim_death1 = 4; +const float knight_anim_death2 = 5; +const float knight_anim_charge1 = 6; +const float knight_anim_magic1 = 7; +const float knight_anim_magic2 = 8; +const float knight_anim_charge2 = 9; +const float knight_anim_slice = 10; +const float knight_anim_smash = 11; +const float knight_anim_wattack = 12; +const float knight_anim_magic3 = 13; + +.float knight_cycles; + +void knight_inferno() +{ + if not(self.enemy) + return; + + traceline((self.absmin + self.absmax) * 0.5, (self.enemy.absmin + self.enemy.absmax) * 0.5, TRUE, world); + if (trace_fraction != 1) + return; // not visible + + self.enemy.effects |= EF_MUZZLEFLASH; + sound(self.enemy, CHAN_AUTO, "player/lava.wav", 1, ATTN_NORM); + + if(vlen(self.enemy.origin - self.origin) <= 2000) + Fire_AddDamage(self.enemy, self, autocvar_g_monster_knight_inferno_damage * monster_skill, autocvar_g_monster_knight_inferno_damagetime, DEATH_MONSTER_KNIGHT_INFERNO); +} + +void knight_fireball_explode() +{ + entity e; + if(self) + { + pointparticles(particleeffectnum("fireball_explode"), self.origin, '0 0 0', 1); + + RadiusDamage(self, self.realowner, autocvar_g_monster_knight_fireball_damage, autocvar_g_monster_knight_fireball_edgedamage, autocvar_g_monster_knight_fireball_force, world, autocvar_g_monster_knight_fireball_radius, self.projectiledeathtype, world); + + for(e = world; (e = findfloat(e, takedamage, DAMAGE_AIM)); ) if(vlen(e.origin - self.origin) <= autocvar_g_monster_knight_fireball_radius) + Fire_AddDamage(e, self, 5 * monster_skill, autocvar_g_monster_knight_inferno_damagetime, self.projectiledeathtype); + + remove(self); + } +} + +void knight_fireball_touch() +{ + PROJECTILE_TOUCH; + + knight_fireball_explode(); +} + +void knight_fireball() +{ + entity missile = spawn(); + vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin); + + monster_makevectors(self.enemy); + + self.effects |= EF_MUZZLEFLASH; + sound(self, CHAN_WEAPON, "weapons/fireball2.wav", 1, ATTN_NORM); + + missile.owner = missile.realowner = self; + missile.solid = SOLID_TRIGGER; + missile.movetype = MOVETYPE_FLYMISSILE; + missile.projectiledeathtype = DEATH_MONSTER_KNIGHT_FBALL; + setsize(missile, '-6 -6 -6', '6 6 6'); + setorigin(missile, self.origin + self.view_ofs + v_forward * 14); + missile.flags = FL_PROJECTILE; + missile.velocity = dir * 400; + missile.avelocity = '300 300 300'; + missile.nextthink = time + 5; + missile.think = knight_fireball_explode; + missile.enemy = self.enemy; + missile.touch = knight_fireball_touch; + CSQCProjectile(missile, TRUE, PROJECTILE_FIREMINE, TRUE); +} + +void knight_spike_explode() +{ + if(self) + { + pointparticles(particleeffectnum("TE_WIZSPIKE"), self.origin, '0 0 0', 1); + + RadiusDamage (self, self.realowner, autocvar_g_monster_knight_spike_damage, autocvar_g_monster_knight_spike_edgedamage, autocvar_g_monster_knight_spike_force, world, autocvar_g_monster_knight_spike_radius, DEATH_MONSTER_KNIGHT_SPIKE, other); + remove(self); + } +} + +void knight_spike_touch() +{ + PROJECTILE_TOUCH; + + knight_spike_explode(); +} + +void knight_spike() +{ + entity missile; + vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin); + + self.effects |= EF_MUZZLEFLASH; + + missile = spawn (); + missile.owner = missile.realowner = self; + missile.solid = SOLID_TRIGGER; + missile.movetype = MOVETYPE_FLYMISSILE; + setsize (missile, '0 0 0', '0 0 0'); + setorigin(missile, self.origin + '0 0 10' + v_forward * 14); + missile.scale = self.scale; + missile.flags = FL_PROJECTILE; + missile.velocity = dir * 400; + missile.avelocity = '300 300 300'; + missile.nextthink = time + 5; + missile.think = knight_spike_explode; + missile.enemy = self.enemy; + missile.touch = knight_spike_touch; + CSQCProjectile(missile, TRUE, PROJECTILE_CRYLINK, TRUE); +} + +void knight_spikes() +{ + self.knight_cycles += 1; + knight_spike(); + + if(self.knight_cycles <= 7) + defer(0.1, knight_spikes); +} + +float knight_attack_ranged() +{ + if not(self.flags & FL_ONGROUND) + return FALSE; + + self.knight_cycles = 0; + + RandomSelection_Init(); + RandomSelection_Add(world, 1, "", autocvar_g_monster_knight_fireball_chance, 1); + RandomSelection_Add(world, 2, "", autocvar_g_monster_knight_inferno_chance, 1); + RandomSelection_Add(world, 3, "", autocvar_g_monster_knight_spike_chance, 1); + if(self.health >= 100) RandomSelection_Add(world, 4, "", ((vlen(self.enemy.origin - self.origin) > autocvar_g_monster_knight_jump_dist) ? 1 : autocvar_g_monster_knight_jump_chance), 1); + + switch(RandomSelection_chosen_float) + { + case 1: + { + monsters_setframe(knight_anim_magic2); + self.attack_finished_single = time + 2; + defer(0.4, knight_fireball); + + return TRUE; + } + case 2: + { + self.attack_finished_single = time + 3; + defer(0.5, knight_inferno); + return TRUE; + } + case 3: + { + monsters_setframe(knight_anim_magic3); + self.attack_finished_single = time + 3; + defer(0.4, knight_spikes); + + return TRUE; + } + case 4: + { + float er = vlen(self.enemy.origin - self.origin); + + if(er >= 400 && er < 1200) + if(findtrajectorywithleading(self.origin, self.mins, self.maxs, self.enemy, 1000, 0, 10, 0, self)) + { + self.velocity = findtrajectory_velocity; + Damage(self.enemy, self, self, autocvar_g_monster_knight_jump_damage * monster_skill, DEATH_MONSTER_KNIGHT_CRUSH, self.enemy.origin, normalize(self.enemy.origin - self.origin)); + self.attack_finished_single = time + 2; + return TRUE; + } + return FALSE; + } + } + + return FALSE; +} + +float knight_attack(float attack_type) +{ + switch(attack_type) + { + case MONSTER_ATTACK_MELEE: + { + float anim; + if(random() < 0.3) + anim = knight_anim_slice; + else if(random() < 0.6) + anim = knight_anim_smash; + else + anim = knight_anim_wattack; + + monsters_setframe(anim); + self.attack_finished_single = time + 0.7; + monster_melee(self.enemy, autocvar_g_monster_knight_melee_damage, 0.3, DEATH_MONSTER_KNIGHT_MELEE, TRUE); + + return TRUE; + } + case MONSTER_ATTACK_RANGED: + { + if(knight_attack_ranged()) + return TRUE; + } + } + + return FALSE; +} + +void spawnfunc_monster_knight() +{ + if not(autocvar_g_monster_knight) { remove(self); return; } + + self.classname = "monster_knight"; + + self.monster_spawnfunc = spawnfunc_monster_knight; + + if(Monster_CheckAppearFlags(self)) + return; + + if not(monster_initialize(MON_KNIGHT, FALSE)) { remove(self); return; } +} + +// compatibility with old spawns +void spawnfunc_monster_hell_knight() { spawnfunc_monster_knight(); } + +float m_knight(float req) +{ + switch(req) + { + case MR_THINK: + { + monster_move(autocvar_g_monster_knight_speed_run, autocvar_g_monster_knight_speed_walk, 100, knight_anim_run, knight_anim_walk, knight_anim_stand); + return TRUE; + } + case MR_DEATH: + { + float chance = random(); + monsters_setframe((random() > 0.5) ? knight_anim_death1 : knight_anim_death2); + if(chance < 0.10 || self.flags & MONSTERFLAG_MINIBOSS) + if(self.candrop) + { + self.superweapons_finished = time + autocvar_g_balance_superweapons_time + 5; // give the player a few seconds to find the weapon + self.weapon = WEP_FIREBALL; + } + return TRUE; + } + case MR_SETUP: + { + if not(self.health) self.health = autocvar_g_monster_knight_health; + + self.monster_attackfunc = knight_attack; + monsters_setframe(knight_anim_stand); + + return TRUE; + } + case MR_INIT: + { + precache_sound ("player/lava.wav"); + return TRUE; + } + } + + return TRUE; +} + +#endif // SVQC +#ifdef CSQC +float m_knight(float req) +{ + switch(req) + { + case MR_DEATH: + { + // nothing + return TRUE; + } + case MR_INIT: + { + precache_model ("models/monsters/hknight.mdl"); + return TRUE; + } + } + + return TRUE; +} + +#endif // CSQC +#endif // REGISTER_MONSTER diff --git a/qcsrc/common/monsters/monster/mage.qc b/qcsrc/common/monsters/monster/mage.qc new file mode 100644 index 000000000..f62892f8d --- /dev/null +++ b/qcsrc/common/monsters/monster/mage.qc @@ -0,0 +1,449 @@ +#ifdef REGISTER_MONSTER +REGISTER_MONSTER( +/* MON_##id */ MAGE, +/* function */ m_mage, +/* spawnflags */ 0, +/* mins,maxs */ '-36 -36 -24', '36 36 50', +/* model */ "mage.dpm", +/* netname */ "mage", +/* fullname */ _("Mage") +); + +#else +#ifdef SVQC +float autocvar_g_monster_mage; +float autocvar_g_monster_mage_health; +float autocvar_g_monster_mage_speed; +float autocvar_g_monster_mage_attack_spike_damage; +float autocvar_g_monster_mage_attack_spike_radius; +float autocvar_g_monster_mage_attack_spike_delay; +float autocvar_g_monster_mage_attack_melee_damage; +float autocvar_g_monster_mage_attack_melee_delay; +float autocvar_g_monster_mage_heal_self; +float autocvar_g_monster_mage_heal_friends; +float autocvar_g_monster_mage_heal_minhealth; +float autocvar_g_monster_mage_heal_range; +float autocvar_g_monster_mage_heal_delay; +float autocvar_g_monster_mage_shield_time; +float autocvar_g_monster_mage_shield_delay; +float autocvar_g_monster_mage_shield_blockpercent; +float autocvar_g_monster_mage_attack_grenade_damage; +float autocvar_g_monster_mage_attack_grenade_edgedamage; +float autocvar_g_monster_mage_attack_grenade_radius; +float autocvar_g_monster_mage_attack_grenade_lifetime; +float autocvar_g_monster_mage_attack_grenade_force; +float autocvar_g_monster_mage_attack_grenade_chance; + +const float mage_anim_idle = 0; +const float mage_anim_walk = 1; +const float mage_anim_attack = 2; +const float mage_anim_pain = 3; +const float mage_anim_death = 4; +const float mage_anim_run = 5; + +void() mage_heal; +void() mage_shield; +void() mage_shield_die; + +float friend_needshelp(entity e) +{ + if(e == world) + return FALSE; + if(e.health <= 0) + return FALSE; + if(vlen(e.origin - self.origin) > autocvar_g_monster_mage_heal_range) + return FALSE; + if(IsDifferentTeam(e, self)) + return FALSE; + if(e.frozen) + return FALSE; + if(!IS_PLAYER(e)) + return (e.health < e.max_health); + if(e.items & IT_INVINCIBLE) + return FALSE; + + switch(self.skin) + { + case 0: + { + if(e.health < autocvar_g_balance_health_regenstable) + return TRUE; + break; + } + case 1: + { + if((e.ammo_cells && e.ammo_cells < g_pickup_cells_max) || (e.ammo_rockets && e.ammo_rockets < g_pickup_rockets_max) || (e.ammo_nails && e.ammo_nails < g_pickup_nails_max) || (e.ammo_shells && e.ammo_shells < g_pickup_shells_max)) + return TRUE; + break; + } + case 2: + { + if(e.armorvalue < autocvar_g_balance_armor_regenstable) + return TRUE; + break; + } + case 3: + { + if(e.health > 0) + return TRUE; + break; + } + } + + return FALSE; +} + +void mageattack_melee() +{ + monster_melee(self.enemy, autocvar_g_monster_mage_attack_melee_damage, 0.3, DEATH_MONSTER_MAGE, TRUE); +} + +void mage_grenade_explode() +{ + pointparticles(particleeffectnum("explosion_small"), self.origin, '0 0 0', 1); + + sound(self, CH_SHOTS, "weapons/grenade_impact.wav", VOL_BASE, ATTN_NORM); + RadiusDamage (self, self.realowner, autocvar_g_monster_mage_attack_grenade_damage, autocvar_g_monster_mage_attack_grenade_edgedamage, autocvar_g_monster_mage_attack_grenade_radius, world, autocvar_g_monster_mage_attack_grenade_force, DEATH_MONSTER_MAGE, other); + remove(self); +} + +void mage_grenade_touch() +{ + if(IS_PLAYER(other)) + { + PROJECTILE_TOUCH; + mage_grenade_explode(); + return; + } +} + +void mage_throw_itemgrenade() +{ + makevectors(self.angles); + + W_SetupShot_ProjectileSize (self, '-64 -64 -64', '64 64 64', FALSE, 4, "", CH_WEAPON_A, autocvar_g_monster_mage_attack_grenade_damage); + w_shotdir = v_forward; // no TrueAim for grenades please + + entity gren = spawn (); + gren.owner = gren.realowner = self; + gren.classname = "grenade"; + gren.bot_dodge = FALSE; + gren.movetype = MOVETYPE_BOUNCE; + gren.solid = SOLID_TRIGGER; + gren.projectiledeathtype = DEATH_MONSTER_MAGE; + setorigin(gren, w_shotorg); + setsize(gren, '-64 -64 -64', '64 64 64'); + + gren.nextthink = time + autocvar_g_monster_mage_attack_grenade_lifetime; + gren.think = mage_grenade_explode; + gren.use = mage_grenade_explode; + gren.touch = mage_grenade_touch; + + gren.missile_flags = MIF_SPLASH | MIF_ARC; + W_SETUPPROJECTILEVELOCITY_UP(gren, g_monster_mage_attack_grenade); + + gren.flags = FL_PROJECTILE; + + setmodel(gren, "models/items/g_h50.md3"); + + self.attack_finished_single = time + 1.5; +} + +void mage_spike_explode() +{ + self.event_damage = func_null; + + pointparticles(particleeffectnum("explosion_small"), self.origin, '0 0 0', 1); + RadiusDamage (self, self.realowner, autocvar_g_monster_mage_attack_spike_damage, autocvar_g_monster_mage_attack_spike_damage * 0.5, autocvar_g_monster_mage_attack_spike_radius, world, 0, DEATH_MONSTER_MAGE, other); + + remove (self); +} + +void mage_spike_touch() +{ + PROJECTILE_TOUCH; + + mage_spike_explode(); +} + +void mage_spike_think() +{ + if(self.enemy.health <= 0 || self.owner.health <= 0 || time >= self.ltime) + { + mage_spike_explode(); + return; + } + + vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin); + + UpdateCSQCProjectile(self); + + if (monster_skill == 3) + self.velocity = dir * 350; + else + self.velocity = dir * 250; + + self.nextthink = time + 0.2; + self.think = mage_spike_think; +} + +void mage_spike() +{ + entity missile; + vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin); + + makevectors(self.angles); + + missile = spawn (); + missile.owner = missile.realowner = self; + missile.think = mage_spike_think; + missile.ltime = time + 7; + missile.nextthink = time; + missile.solid = SOLID_BBOX; + missile.movetype = MOVETYPE_FLYMISSILE; + missile.flags = FL_PROJECTILE; + setorigin(missile, self.origin + v_forward * 14 + '0 0 30' + v_right * -14); + setsize (missile, '0 0 0', '0 0 0'); + missile.velocity = dir * 400; + missile.avelocity = '300 300 300'; + missile.enemy = self.enemy; + missile.touch = mage_spike_touch; + + CSQCProjectile(missile, TRUE, PROJECTILE_MAGE_SPIKE, TRUE); +} + +void mage_heal() +{ + entity head; + float washealed = FALSE; + + for(head = world; (head = findfloat(head, monster_attack, TRUE)); ) if(friend_needshelp(head)) + { + washealed = TRUE; + string fx = ""; + if(IS_PLAYER(head)) + { + switch(self.skin) + { + case 0: + if(head.health < autocvar_g_balance_health_regenstable) head.health = bound(0, head.health + autocvar_g_monster_mage_heal_friends, autocvar_g_balance_health_regenstable); + fx = "healing_fx"; + break; + case 1: + if(head.ammo_cells) head.ammo_cells = bound(head.ammo_cells, head.ammo_cells + 1, g_pickup_cells_max); + if(head.ammo_rockets) head.ammo_rockets = bound(head.ammo_rockets, head.ammo_rockets + 1, g_pickup_rockets_max); + if(head.ammo_shells) head.ammo_shells = bound(head.ammo_shells, head.ammo_shells + 2, g_pickup_shells_max); + if(head.ammo_nails) head.ammo_nails = bound(head.ammo_nails, head.ammo_nails + 5, g_pickup_nails_max); + fx = "ammoregen_fx"; + break; + case 2: + if(head.armorvalue < autocvar_g_balance_armor_regenstable) + { + head.armorvalue = bound(0, head.armorvalue + autocvar_g_monster_mage_heal_friends, autocvar_g_balance_armor_regenstable); + fx = "armorrepair_fx"; + } + break; + case 3: + head.health = bound(0, head.health - ((head == self) ? autocvar_g_monster_mage_heal_self : autocvar_g_monster_mage_heal_friends), autocvar_g_balance_health_regenstable); + fx = "rage"; + break; + } + + pointparticles(particleeffectnum(fx), head.origin, '0 0 0', 1); + } + else + { + pointparticles(particleeffectnum("healing_fx"), head.origin, '0 0 0', 1); + head.health = bound(0, head.health + autocvar_g_monster_mage_heal_friends, head.max_health); + WaypointSprite_UpdateHealth(head.sprite, head.health); + } + } + + if(washealed) + { + monsters_setframe(mage_anim_attack); + self.attack_finished_single = time + autocvar_g_monster_mage_heal_delay; + } +} + +void mage_shield_die() +{ + if not(self.weaponentity) + return; // why would this be called without a shield? + + self.armorvalue = 1; + + remove(self.weaponentity); + + self.weaponentity = world; +} + +void mage_shield() +{ + if(self.weaponentity) + return; // already have a shield + + entity shield = spawn(); + + shield.owner = self; + shield.team = self.team; + shield.ltime = time + autocvar_g_monster_mage_shield_time; + shield.health = 70; + shield.classname = "shield"; + shield.effects = EF_ADDITIVE; + shield.movetype = MOVETYPE_NOCLIP; + shield.solid = SOLID_TRIGGER; + shield.avelocity = '7 0 11'; + shield.scale = self.scale * 0.6; + + setattachment(shield, self, ""); + setmodel(shield, "models/ctf/shield.md3"); + setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs); + + self.weaponentity = shield; + + self.lastshielded = time + autocvar_g_monster_mage_shield_delay; + + monsters_setframe(mage_anim_attack); + self.attack_finished_single = time + 1; + + self.armorvalue = autocvar_g_monster_mage_shield_blockpercent / 100; +} + +float mage_attack(float attack_type) +{ + switch(attack_type) + { + case MONSTER_ATTACK_MELEE: + { + monsters_setframe(mage_anim_attack); + self.attack_finished_single = time + autocvar_g_monster_mage_attack_melee_delay; + defer(0.2, mageattack_melee); + + return TRUE; + } + case MONSTER_ATTACK_RANGED: + { + if(random() < autocvar_g_monster_mage_attack_grenade_chance / 100) + { + mage_throw_itemgrenade(); + return TRUE; + } + + monsters_setframe(mage_anim_attack); + self.attack_finished_single = time + autocvar_g_monster_mage_attack_spike_delay; + defer(0.2, mage_spike); + + return TRUE; + } + } + + return FALSE; +} + +void spawnfunc_monster_mage() +{ + if not(autocvar_g_monster_mage) { remove(self); return; } + + self.classname = "monster_mage"; + + self.monster_spawnfunc = spawnfunc_monster_mage; + + if(Monster_CheckAppearFlags(self)) + return; + + if not(monster_initialize(MON_MAGE, FALSE)) { remove(self); return; } +} + +// compatibility with old spawns +void spawnfunc_monster_shalrath() { spawnfunc_monster_mage(); } + +float m_mage(float req) +{ + switch(req) + { + case MR_THINK: + { + entity head; + float need_help = FALSE; + + FOR_EACH_PLAYER(head) + if(friend_needshelp(head)) + { + need_help = TRUE; + break; // found 1 player near us who is low on health + } + if(!need_help) + FOR_EACH_MONSTER(head) + if(head != self) + if(friend_needshelp(head)) + { + need_help = TRUE; + break; // found 1 player near us who is low on health + } + + if(self.weaponentity) + if(time >= self.weaponentity.ltime) + mage_shield_die(); + + if(self.health < autocvar_g_monster_mage_heal_minhealth || need_help) + if(time >= self.attack_finished_single) + if(random() < 0.5) + mage_heal(); + + if(self.enemy) + if(self.health < self.max_health) + if(time >= self.lastshielded) + if(random() < 0.5) + mage_shield(); + + monster_move(autocvar_g_monster_mage_speed, autocvar_g_monster_mage_speed, 50, mage_anim_walk, mage_anim_run, mage_anim_idle); + return TRUE; + } + case MR_DEATH: + { + monsters_setframe(mage_anim_death); + return TRUE; + } + case MR_SETUP: + { + if not(self.health) self.health = autocvar_g_monster_mage_health; + + self.monster_attackfunc = mage_attack; + monsters_setframe(mage_anim_walk); + + return TRUE; + } + case MR_INIT: + { + // nothing + return TRUE; + } + } + + return TRUE; +} + +#endif // SVQC +#ifdef CSQC +float m_mage(float req) +{ + switch(req) + { + case MR_DEATH: + { + // nothing + return TRUE; + } + case MR_INIT: + { + precache_model ("models/monsters/mage.dpm"); + return TRUE; + } + } + + return TRUE; +} + +#endif // CSQC +#endif // REGISTER_MONSTER diff --git a/qcsrc/common/monsters/monster/shambler.qc b/qcsrc/common/monsters/monster/shambler.qc new file mode 100644 index 000000000..54a6ec0c3 --- /dev/null +++ b/qcsrc/common/monsters/monster/shambler.qc @@ -0,0 +1,175 @@ +#ifdef REGISTER_MONSTER +REGISTER_MONSTER( +/* MON_##id */ SHAMBLER, +/* function */ m_shambler, +/* spawnflags */ MONSTER_SIZE_BROKEN, +/* mins,maxs */ '-41 -41 -31', '41 41 65', +/* model */ "shambler.mdl", +/* netname */ "shambler", +/* fullname */ _("Shambler") +); + +#else +#ifdef SVQC +float autocvar_g_monster_shambler; +float autocvar_g_monster_shambler_health; +float autocvar_g_monster_shambler_damage; +float autocvar_g_monster_shambler_attack_lightning_damage; +float autocvar_g_monster_shambler_attack_claw_damage; +float autocvar_g_monster_shambler_speed_walk; +float autocvar_g_monster_shambler_speed_run; + +const float shambler_anim_stand = 0; +const float shambler_anim_walk = 1; +const float shambler_anim_run = 2; +const float shambler_anim_smash = 3; +const float shambler_anim_swingr = 4; +const float shambler_anim_swingl = 5; +const float shambler_anim_magic = 6; +const float shambler_anim_pain = 7; +const float shambler_anim_death = 8; + +void shambler_smash() +{ + monster_melee(self.enemy, autocvar_g_monster_shambler_damage, 0.3, DEATH_MONSTER_SHAMBLER_SMASH, TRUE); +} + +void shambler_delayedsmash() +{ + monsters_setframe(shambler_anim_smash); + defer(0.7, shambler_smash); + self.attack_finished_single = time + 1.1; +} + +void shambler_swing() +{ + float r = (random() < 0.5); + monsters_setframe((r) ? shambler_anim_swingr : shambler_anim_swingl); + monster_melee(self.enemy, autocvar_g_monster_shambler_attack_claw_damage, 0.3, DEATH_MONSTER_SHAMBLER_CLAW, TRUE); + self.attack_finished_single = time + 0.8; + if(r) + defer(0.5, shambler_swing); +} + +void CastLightning() +{ + local vector org, dir; + //vector v = '0 0 0'; + + self.effects |= EF_MUZZLEFLASH; + + org = self.origin + '0 0 40'; + + dir = self.enemy.origin + '0 0 16' - org; + dir = normalize (dir); + + traceline (org, self.origin + dir * 1000, TRUE, self); + + FireRailgunBullet (org, org + dir * 1000, autocvar_g_monster_shambler_attack_lightning_damage * monster_skill, 0, 0, 0, 0, 0, DEATH_MONSTER_SHAMBLER_ZAP); + + // teamcolor / hit beam effect + //v = WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos); + //WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3"), org, v); + + te_csqc_lightningarc(org, trace_endpos); +} + +float shambler_attack(float attack_type) +{ + switch(attack_type) + { + case MONSTER_ATTACK_MELEE: + { + float chance = random(); + + if(chance > 0.6) + shambler_delayedsmash(); + else + shambler_swing(); + + return TRUE; + } + case MONSTER_ATTACK_RANGED: + { + monsters_setframe(shambler_anim_magic); + self.attack_finished_single = time + 1.1; + defer(0.6, CastLightning); + + return TRUE; + } + } + + return FALSE; +} + +void spawnfunc_monster_shambler() +{ + if not(autocvar_g_monster_shambler) { remove(self); return; } + + self.classname = "monster_shambler"; + + self.monster_spawnfunc = spawnfunc_monster_shambler; + + if(Monster_CheckAppearFlags(self)) + return; + + if not(monster_initialize(MON_SHAMBLER, FALSE)) { remove(self); return; } +} + +float m_shambler(float req) +{ + switch(req) + { + case MR_THINK: + { + monster_move(autocvar_g_monster_shambler_speed_run, autocvar_g_monster_shambler_speed_walk, 300, shambler_anim_run, shambler_anim_walk, shambler_anim_stand); + return TRUE; + } + case MR_DEATH: + { + monsters_setframe(shambler_anim_death); + return TRUE; + } + case MR_SETUP: + { + if not(self.health) self.health = autocvar_g_monster_shambler_health; + + self.monster_attackfunc = shambler_attack; + monsters_setframe(shambler_anim_stand); + self.weapon = WEP_NEX; + + return TRUE; + } + case MR_INIT: + { + // nothing + return TRUE; + } + } + + return TRUE; +} + +#endif // SVQC +#ifdef CSQC +float m_shambler(float req) +{ + switch(req) + { + case MR_DEATH: + { + // nothing + return TRUE; + } + case MR_INIT: + { + precache_model ("models/monsters/shambler.mdl"); + return TRUE; + } + } + + return TRUE; +} + +#endif // CSQC +#endif // REGISTER_MONSTER diff --git a/qcsrc/common/monsters/monster/slime.qc b/qcsrc/common/monsters/monster/slime.qc new file mode 100644 index 000000000..5e206f227 --- /dev/null +++ b/qcsrc/common/monsters/monster/slime.qc @@ -0,0 +1,163 @@ +#ifdef REGISTER_MONSTER +REGISTER_MONSTER( +/* MON_##id */ SLIME, +/* function */ m_slime, +/* spawnflags */ 0, +/* mins,maxs */ '-16 -16 -24', '16 16 16', +/* model */ "slime.dpm", +/* netname */ "slime", +/* fullname */ _("Slime") +); + +#else +#ifdef SVQC +float autocvar_g_monster_slime; +float autocvar_g_monster_slime_health; +float autocvar_g_monster_slime_speed_walk; +float autocvar_g_monster_slime_speed_run; + +const float slime_anim_walk = 0; +const float slime_anim_idle = 1; +const float slime_anim_jump = 2; +const float slime_anim_fly = 3; +const float slime_anim_die = 4; +const float slime_anim_pain = 5; + +void slime_touch_jump() +{ + if(self.health > 0) + if(other.health > 0) + if(other.takedamage) + if(vlen(self.velocity) > 200) + { + Damage (self, world, world, self.health + self.max_health + 200, DEATH_MONSTER_SLIME, self.origin, '0 0 0'); + + return; + } + + if(trace_dphitcontents) + { + self.touch = MonsterTouch; + self.movetype = MOVETYPE_WALK; + } +} + +float slime_attack(float attack_type) +{ + switch(attack_type) + { + case MONSTER_ATTACK_MELEE: + case MONSTER_ATTACK_RANGED: + { + makevectors(self.angles); + if(monster_leap(slime_anim_jump, slime_touch_jump, v_forward * 600 + '0 0 200', 0.5)) + return TRUE; + } + } + + return FALSE; +} + +void slime_explode() +{ + RadiusDamage(self, self, 250 * monster_skill, 15, 250 * (monster_skill * 0.7), world, 250, DEATH_MONSTER_SLIME, world); + pointparticles(particleeffectnum("explosion_medium"), self.origin, '0 0 0', 1); + sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTN_NORM); + + setmodel(self, ""); +} + +void slime_dead() +{ + self.health = -100; // gibbed + slime_explode(); + + Monster_CheckDropCvars ("slime"); // TODO: add a special function to drop items after death + + self.deadflag = DEAD_DEAD; + self.think = Monster_Fade; + self.nextthink = time + 0.1; +} + +void spawnfunc_monster_slime() +{ + if not(autocvar_g_monster_slime) { remove(self); return; } + + self.classname = "monster_slime"; + + self.monster_spawnfunc = spawnfunc_monster_slime; + + if(Monster_CheckAppearFlags(self)) + return; + + if not(monster_initialize(MON_SLIME, FALSE)) { remove(self); return; } +} + +// compatibility with old spawns +void spawnfunc_monster_tarbaby() { spawnfunc_monster_slime(); } + +float m_slime(float req) +{ + switch(req) + { + case MR_THINK: + { + monster_move(autocvar_g_monster_slime_speed_run, autocvar_g_monster_slime_speed_walk, 20, slime_anim_walk, slime_anim_walk, slime_anim_idle); + return TRUE; + } + case MR_DEATH: + { + self.think = slime_dead; + self.nextthink = time; + self.event_damage = func_null; + self.movetype = MOVETYPE_NONE; + self.takedamage = DAMAGE_NO; + self.enemy = world; + self.health = 0; + + self.SendFlags |= MSF_MOVE | MSF_STATUS; + + return TRUE; + } + case MR_SETUP: + { + if not(self.health) self.health = autocvar_g_monster_slime_health; + + self.monster_attackfunc = slime_attack; + monsters_setframe(slime_anim_idle); + + return TRUE; + } + case MR_INIT: + { + // nothing + return TRUE; + } + } + + return TRUE; +} + +#endif // SVQC +#ifdef CSQC +float m_slime(float req) +{ + switch(req) + { + case MR_DEATH: + { + // nothing + return TRUE; + } + case MR_INIT: + { + precache_model ("models/monsters/slime.dpm"); + return TRUE; + } + } + + return TRUE; +} + +#endif // CSQC +#endif // REGISTER_MONSTER diff --git a/qcsrc/common/monsters/monster/spider.qc b/qcsrc/common/monsters/monster/spider.qc new file mode 100644 index 000000000..f98ee8f94 --- /dev/null +++ b/qcsrc/common/monsters/monster/spider.qc @@ -0,0 +1,234 @@ +#ifdef REGISTER_MONSTER +REGISTER_MONSTER( +/* MON_##id */ SPIDER, +/* function */ m_spider, +/* spawnflags */ 0, +/* mins,maxs */ '-18 -18 -25', '18 18 30', +/* model */ "spider.dpm", +/* netname */ "spider", +/* fullname */ _("Spider") +); + +#else +#ifdef SVQC +float autocvar_g_monster_spider; +float autocvar_g_monster_spider_stopspeed; +float autocvar_g_monster_spider_attack_leap_delay; +float autocvar_g_monster_spider_attack_stand_damage; +float autocvar_g_monster_spider_attack_stand_delay; +float autocvar_g_monster_spider_attack_fire_time; +float autocvar_g_monster_spider_health; +float autocvar_g_monster_spider_speed_walk; +float autocvar_g_monster_spider_speed_run; +float autocvar_g_monster_spider_attack_type; + +const float spider_anim_idle = 0; +const float spider_anim_walk = 1; +const float spider_anim_attack = 2; +const float spider_anim_attack2 = 3; + +.float spider_type; // used to switch between fire & ice attacks +const float SPIDER_TYPE_ICE = 0; +const float SPIDER_TYPE_FIRE = 1; + +void spider_web_explode() +{ + entity e; + if(self) + { + float damg = 0, edamg = 0, rad = 1; + switch(self.realowner.spider_type) + { + case SPIDER_TYPE_ICE: + rad = 25; + pointparticles(particleeffectnum("electro_impact"), self.origin, '0 0 0', 1); + break; + case SPIDER_TYPE_FIRE: + pointparticles(particleeffectnum("fireball_explode"), self.origin, '0 0 0', 1); + damg = 15; + rad = 25; + edamg = 6; + break; + } + + RadiusDamage(self, self.realowner, damg, edamg, 0, world, rad, DEATH_MONSTER_SPIDER_FIRE, world); // ice deals no damage anyway + + for(e = findradius(self.origin, rad); e; e = e.chain) if(e.takedamage && e.deadflag == DEAD_NO) if(e.health > 0) + { + switch(self.realowner.spider_type) + { + case SPIDER_TYPE_ICE: + Freeze(e, 0.3, 2, FALSE); + break; + case SPIDER_TYPE_FIRE: + Fire_AddDamage(e, self.realowner, 5 * monster_skill, autocvar_g_monster_spider_attack_fire_time, DEATH_MONSTER_SPIDER_FIRE); + break; + } + } + + remove(self); + } +} + +void spider_web_touch() +{ + PROJECTILE_TOUCH; + + spider_web_explode(); +} + +void spider_shootweb(float ptype) +{ + float p = 0; + string snd = ""; + switch(ptype) + { + case SPIDER_TYPE_ICE: + p = PROJECTILE_ELECTRO; + snd = "weapons/electro_fire2.wav"; + break; + case SPIDER_TYPE_FIRE: + p = PROJECTILE_FIREMINE; + snd = "weapons/fireball_fire.wav"; + break; + } + + vector fmins = '-4 -4 -4', fmaxs = '4 4 4'; + + W_SetupShot_ProjectileSize(self, fmins, fmaxs, FALSE, 2, snd, CH_WEAPON_A, 0); + + w_shotdir = v_forward; // no TrueAim for grenades please + + entity proj = spawn (); + proj.classname = "plasma"; + proj.owner = proj.realowner = self; + proj.use = spider_web_touch; + proj.think = adaptor_think2use_hittype_splash; + proj.bot_dodge = TRUE; + proj.bot_dodgerating = 0; + proj.nextthink = time + 5; + PROJECTILE_MAKETRIGGER(proj); + proj.projectiledeathtype = DEATH_MONSTER_SPIDER_FIRE; + setorigin(proj, w_shotorg); + + //proj.glow_size = 50; + //proj.glow_color = 45; + proj.movetype = MOVETYPE_BOUNCE; + W_SETUPPROJECTILEVELOCITY_UP(proj, g_monster_spider_attack_web); + proj.touch = spider_web_touch; + setsize(proj, fmins, fmaxs); + proj.takedamage = DAMAGE_NO; + proj.damageforcescale = 0; + proj.health = 500; + proj.event_damage = func_null; + proj.flags = FL_PROJECTILE; + proj.damagedbycontents = TRUE; + + proj.bouncefactor = 0.3; + proj.bouncestop = 0.05; + proj.missile_flags = MIF_SPLASH | MIF_ARC; + + CSQCProjectile(proj, TRUE, p, TRUE); +} + +float spider_attack(float attack_type) +{ + switch(attack_type) + { + case MONSTER_ATTACK_MELEE: + { + monster_melee(self.enemy, autocvar_g_monster_spider_attack_stand_damage, 0.3, DEATH_MONSTER_SPIDER, TRUE); + monsters_setframe((random() > 0.5) ? spider_anim_attack : spider_anim_attack2); + self.attack_finished_single = time + autocvar_g_monster_spider_attack_stand_delay; + + return TRUE; + } + case MONSTER_ATTACK_RANGED: + { + if(self.enemy.frozen) + return FALSE; + + monsters_setframe(spider_anim_attack2); + self.attack_finished_single = time + autocvar_g_monster_spider_attack_leap_delay; + monster_makevectors(self.enemy); + spider_shootweb(self.spider_type); + + return TRUE; + } + } + + return FALSE; +} + +void spawnfunc_monster_spider() +{ + if not(autocvar_g_monster_spider) { remove(self); return; } + + self.classname = "monster_spider"; + + self.monster_spawnfunc = spawnfunc_monster_spider; + + if(Monster_CheckAppearFlags(self)) + return; + + if not(monster_initialize(MON_SPIDER, FALSE)) { remove(self); return; } +} + +float m_spider(float req) +{ + switch(req) + { + case MR_THINK: + { + monster_move(autocvar_g_monster_spider_speed_run, autocvar_g_monster_spider_speed_walk, autocvar_g_monster_spider_stopspeed, spider_anim_walk, spider_anim_walk, spider_anim_idle); + return TRUE; + } + case MR_DEATH: + { + monsters_setframe(spider_anim_attack); + self.angles += '180 0 0'; + return TRUE; + } + case MR_SETUP: + { + if not(self.health) self.health = autocvar_g_monster_spider_health; + if not(self.spider_type) self.spider_type = autocvar_g_monster_spider_attack_type; + + self.monster_attackfunc = spider_attack; + monsters_setframe(spider_anim_idle); + + return TRUE; + } + case MR_INIT: + { + // nothing + return TRUE; + } + } + + return TRUE; +} + +#endif // SVQC +#ifdef CSQC +float m_spider(float req) +{ + switch(req) + { + case MR_DEATH: + { + self.angles += '180 0 0'; // TODO: use the server side angles instead? + return TRUE; + } + case MR_INIT: + { + precache_model ("models/monsters/spider.dpm"); + return TRUE; + } + } + + return TRUE; +} + +#endif // CSQC +#endif // REGISTER_MONSTER diff --git a/qcsrc/common/monsters/monster/stingray.qc b/qcsrc/common/monsters/monster/stingray.qc new file mode 100644 index 000000000..08c7635c3 --- /dev/null +++ b/qcsrc/common/monsters/monster/stingray.qc @@ -0,0 +1,112 @@ +#ifdef REGISTER_MONSTER +REGISTER_MONSTER( +/* MON_##id */ STINGRAY, +/* function */ m_stingray, +/* spawnflags */ MONSTER_TYPE_SWIM | MONSTER_SIZE_BROKEN, +/* mins,maxs */ '-20 -20 -31', '20 20 20', +/* model */ "fish.mdl", +/* netname */ "stingray", +/* fullname */ _("Stingray") +); + +#else +#ifdef SVQC +float autocvar_g_monster_stingray; +float autocvar_g_monster_stingray_health; +float autocvar_g_monster_stingray_damage; +float autocvar_g_monster_stingray_speed_walk; +float autocvar_g_monster_stingray_speed_run; + +const float stingray_anim_attack = 0; +const float stingray_anim_death = 1; +const float stingray_anim_swim = 2; +const float stingray_anim_pain = 3; + +float stingray_attack(float attack_type) +{ + switch(attack_type) + { + case MONSTER_ATTACK_MELEE: + { + monsters_setframe(stingray_anim_attack); + self.attack_finished_single = time + 0.5; + monster_melee(self.enemy, autocvar_g_monster_stingray_damage, 0.1, DEATH_MONSTER_STINGRAY, FALSE); + + return TRUE; + } + case MONSTER_ATTACK_RANGED: + } + + return FALSE; +} + +void spawnfunc_monster_stingray() +{ + if not(autocvar_g_monster_stingray) { remove(self); return; } + + self.classname = "monster_stingray"; + + self.monster_spawnfunc = spawnfunc_monster_stingray; + + if(Monster_CheckAppearFlags(self)) + return; + + if not(monster_initialize(MON_STINGRAY, TRUE)) { remove(self); return; } +} + +float m_stingray(float req) +{ + switch(req) + { + case MR_THINK: + { + monster_move(autocvar_g_monster_stingray_speed_run, autocvar_g_monster_stingray_speed_walk, 10, stingray_anim_swim, stingray_anim_swim, stingray_anim_swim); + return TRUE; + } + case MR_DEATH: + { + monsters_setframe(stingray_anim_swim); + return TRUE; + } + case MR_SETUP: + { + if not(self.health) self.health = autocvar_g_monster_stingray_health; + + self.monster_attackfunc = stingray_attack; + monsters_setframe(stingray_anim_death); + + return TRUE; + } + case MR_INIT: + { + // nothing + return TRUE; + } + } + + return TRUE; +} + +#endif // SVQC +#ifdef CSQC +float m_stingray(float req) +{ + switch(req) + { + case MR_DEATH: + { + // nothing + return TRUE; + } + case MR_INIT: + { + precache_model ("models/monsters/fish.mdl"); + return TRUE; + } + } + + return TRUE; +} + +#endif // CSQC +#endif // REGISTER_MONSTER diff --git a/qcsrc/common/monsters/monster/wyvern.qc b/qcsrc/common/monsters/monster/wyvern.qc new file mode 100644 index 000000000..71e1236f4 --- /dev/null +++ b/qcsrc/common/monsters/monster/wyvern.qc @@ -0,0 +1,170 @@ +#ifdef REGISTER_MONSTER +REGISTER_MONSTER( +/* MON_##id */ WYVERN, +/* function */ m_wyvern, +/* spawnflags */ MONSTER_TYPE_FLY | MONSTER_SIZE_BROKEN, +/* mins,maxs */ '-20 -20 -58', '20 20 20', +/* model */ "wizard.mdl", +/* netname */ "wyvern", +/* fullname */ _("Wyvern") +); + +#else +#ifdef SVQC +float autocvar_g_monster_wyvern; +float autocvar_g_monster_wyvern_health; +float autocvar_g_monster_wyvern_speed_walk; +float autocvar_g_monster_wyvern_speed_run; +float autocvar_g_monster_wyvern_fireball_damage; +float autocvar_g_monster_wyvern_fireball_force; +float autocvar_g_monster_wyvern_fireball_radius; +float autocvar_g_monster_wyvern_fireball_edgedamage; +float autocvar_g_monster_wyvern_fireball_damagetime; +float autocvar_g_monster_wyvern_fireball_speed; + +const float wyvern_anim_hover = 0; +const float wyvern_anim_fly = 1; +const float wyvern_anim_magic = 2; +const float wyvern_anim_pain = 3; +const float wyvern_anim_death = 4; + +void wyvern_fireball_explode() +{ + entity e; + if(self) + { + pointparticles(particleeffectnum("fireball_explode"), self.origin, '0 0 0', 1); + + RadiusDamage(self, self.realowner, autocvar_g_monster_wyvern_fireball_damage, autocvar_g_monster_wyvern_fireball_edgedamage, autocvar_g_monster_wyvern_fireball_force, world, autocvar_g_monster_wyvern_fireball_radius, self.projectiledeathtype, world); + + for(e = world; (e = findfloat(e, takedamage, DAMAGE_AIM)); ) if(vlen(e.origin - self.origin) <= autocvar_g_monster_wyvern_fireball_radius) + Fire_AddDamage(e, self, 5 * monster_skill, autocvar_g_monster_wyvern_fireball_damagetime, self.projectiledeathtype); + + remove(self); + } +} + +void wyvern_fireball_touch() +{ + PROJECTILE_TOUCH; + + wyvern_fireball_explode(); +} + +void wyvern_fireball() +{ + entity missile = spawn(); + vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin); + + monster_makevectors(self.enemy); + + missile.owner = missile.realowner = self; + missile.solid = SOLID_TRIGGER; + missile.movetype = MOVETYPE_FLYMISSILE; + missile.projectiledeathtype = DEATH_MONSTER_WYVERN; + setsize(missile, '-6 -6 -6', '6 6 6'); + setorigin(missile, self.origin + self.view_ofs + v_forward * 14); + missile.flags = FL_PROJECTILE; + missile.velocity = dir * autocvar_g_monster_wyvern_fireball_speed; + missile.avelocity = '300 300 300'; + missile.nextthink = time + 5; + missile.think = wyvern_fireball_explode; + missile.enemy = self.enemy; + missile.touch = wyvern_fireball_touch; + CSQCProjectile(missile, TRUE, PROJECTILE_FIREMINE, TRUE); +} + +float wyvern_attack(float attack_type) +{ + switch(attack_type) + { + case MONSTER_ATTACK_MELEE: + case MONSTER_ATTACK_RANGED: + { + self.attack_finished_single = time + 1.2; + + wyvern_fireball(); + + return TRUE; + } + } + + return FALSE; +} + +void spawnfunc_monster_wyvern() +{ + if not(autocvar_g_monster_wyvern) { remove(self); return; } + + self.classname = "monster_wyvern"; + + self.monster_spawnfunc = spawnfunc_monster_wyvern; + + if(Monster_CheckAppearFlags(self)) + return; + + if not(monster_initialize(MON_WYVERN, TRUE)) { remove(self); return; } +} + +// compatibility with old spawns +void spawnfunc_monster_wizard() { spawnfunc_monster_wyvern(); } + +float m_wyvern(float req) +{ + switch(req) + { + case MR_THINK: + { + monster_move(autocvar_g_monster_wyvern_speed_run, autocvar_g_monster_wyvern_speed_walk, 300, wyvern_anim_fly, wyvern_anim_hover, wyvern_anim_hover); + return TRUE; + } + case MR_DEATH: + { + monsters_setframe(wyvern_anim_death); + self.velocity_x = -200 + 400 * random(); + self.velocity_y = -200 + 400 * random(); + self.velocity_z = 100 + 100 * random(); + return TRUE; + } + case MR_SETUP: + { + if not(self.health) self.health = autocvar_g_monster_wyvern_health; + + self.monster_attackfunc = wyvern_attack; + monsters_setframe(wyvern_anim_hover); + + return TRUE; + } + case MR_INIT: + { + // nothing + return TRUE; + } + } + + return TRUE; +} + +#endif // SVQC +#ifdef CSQC +float m_wyvern(float req) +{ + switch(req) + { + case MR_DEATH: + { + // nothing + return TRUE; + } + case MR_INIT: + { + precache_model ("models/monsters/knight.mdl"); + return TRUE; + } + } + + return TRUE; +} + +#endif // CSQC +#endif // REGISTER_MONSTER diff --git a/qcsrc/common/monsters/monster/zombie.qc b/qcsrc/common/monsters/monster/zombie.qc new file mode 100644 index 000000000..9ddfbde58 --- /dev/null +++ b/qcsrc/common/monsters/monster/zombie.qc @@ -0,0 +1,183 @@ +#ifdef REGISTER_MONSTER +REGISTER_MONSTER( +/* MON_##id */ ZOMBIE, +/* function */ m_zombie, +/* spawnflags */ MONSTER_RESPAWN_DEATHPOINT, +/* mins,maxs */ '-18 -18 -25', '18 18 47', +/* model */ "zombie.dpm", +/* netname */ "zombie", +/* fullname */ _("Zombie") +); + +#else +#ifdef SVQC +float autocvar_g_monster_zombie; +float autocvar_g_monster_zombie_stopspeed; +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_speed; +float autocvar_g_monster_zombie_attack_stand_damage; +float autocvar_g_monster_zombie_attack_stand_delay; +float autocvar_g_monster_zombie_health; +float autocvar_g_monster_zombie_speed_walk; +float autocvar_g_monster_zombie_speed_run; + +const float zombie_anim_attackleap = 0; +const float zombie_anim_attackrun1 = 1; +const float zombie_anim_attackrun2 = 2; +const float zombie_anim_attackrun3 = 3; +const float zombie_anim_attackstanding1 = 4; +const float zombie_anim_attackstanding2 = 5; +const float zombie_anim_attackstanding3 = 6; +const float zombie_anim_blockend = 7; +const float zombie_anim_blockstart = 8; +const float zombie_anim_deathback1 = 9; +const float zombie_anim_deathback2 = 10; +const float zombie_anim_deathback3 = 11; +const float zombie_anim_deathfront1 = 12; +const float zombie_anim_deathfront2 = 13; +const float zombie_anim_deathfront3 = 14; +const float zombie_anim_deathleft1 = 15; +const float zombie_anim_deathleft2 = 16; +const float zombie_anim_deathright1 = 17; +const float zombie_anim_deathright2 = 18; +const float zombie_anim_idle = 19; +const float zombie_anim_painback1 = 20; +const float zombie_anim_painback2 = 21; +const float zombie_anim_painfront1 = 22; +const float zombie_anim_painfront2 = 23; +const float zombie_anim_runbackwards = 24; +const float zombie_anim_runbackwardsleft = 25; +const float zombie_anim_runbackwardsright = 26; +const float zombie_anim_runforward = 27; +const float zombie_anim_runforwardleft = 28; +const float zombie_anim_runforwardright = 29; +const float zombie_anim_spawn = 30; + +void zombie_attack_leap_touch() +{ + if (self.health <= 0) + return; + + vector angles_face; + + if(other.takedamage) + { + angles_face = vectoangles(self.moveto - self.origin); + angles_face = normalize(angles_face) * autocvar_g_monster_zombie_attack_leap_force; + Damage(other, self, self, autocvar_g_monster_zombie_attack_leap_damage * monster_skill, DEATH_MONSTER_ZOMBIE_JUMP, other.origin, angles_face); + self.touch = MonsterTouch; // instantly turn it off to stop damage spam + } + + if (trace_dphitcontents) + self.touch = MonsterTouch; +} + +float zombie_attack(float attack_type) +{ + switch(attack_type) + { + case MONSTER_ATTACK_MELEE: + { + float rand = random(), chosen_anim; + + if(rand < 0.33) + chosen_anim = zombie_anim_attackstanding1; + else if(rand < 0.66) + chosen_anim = zombie_anim_attackstanding2; + else + chosen_anim = zombie_anim_attackstanding3; + + monsters_setframe(chosen_anim); + + self.attack_finished_single = time + autocvar_g_monster_zombie_attack_stand_delay; + + monster_melee(self.enemy, autocvar_g_monster_zombie_attack_stand_damage, 0.3, DEATH_MONSTER_ZOMBIE_MELEE, TRUE); + + return TRUE; + } + case MONSTER_ATTACK_RANGED: + { + makevectors(self.angles); + 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 spawnfunc_monster_zombie() +{ + if not(autocvar_g_monster_zombie) { remove(self); return; } + + self.classname = "monster_zombie"; + + self.monster_spawnfunc = spawnfunc_monster_zombie; + + if(Monster_CheckAppearFlags(self)) + return; + + if not(monster_initialize(MON_ZOMBIE, FALSE)) { remove(self); return; } +} + +float m_zombie(float req) +{ + switch(req) + { + case MR_THINK: + { + 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); + return TRUE; + } + case MR_DEATH: + { + monsters_setframe((random() > 0.5) ? zombie_anim_deathback1 : zombie_anim_deathfront1); + return TRUE; + } + case MR_SETUP: + { + if not(self.health) self.health = autocvar_g_monster_zombie_health; + + self.monster_attackfunc = zombie_attack; + monsters_setframe(zombie_anim_spawn); + self.spawn_time = time + 2.1; + self.spawnshieldtime = self.spawn_time; + self.respawntime = 0.1; + + return TRUE; + } + case MR_INIT: + { + // nothing + return TRUE; + } + } + + return TRUE; +} + +#endif // SVQC +#ifdef CSQC +float m_zombie(float req) +{ + switch(req) + { + case MR_DEATH: + { + // nothing + return TRUE; + } + case MR_INIT: + { + precache_model ("models/monsters/zombie.dpm"); + return TRUE; + } + } + + return TRUE; +} + +#endif // CSQC +#endif // REGISTER_MONSTER diff --git a/qcsrc/common/monsters/monsters.qc b/qcsrc/common/monsters/monsters.qc new file mode 100644 index 000000000..706bf5526 --- /dev/null +++ b/qcsrc/common/monsters/monsters.qc @@ -0,0 +1,54 @@ +#ifdef SVQC +#include "lib/defs.qh" +#include "lib/monsters.qc" +#include "lib/spawn.qc" +#endif + +#include "all.qh" + +// MONSTER PLUGIN SYSTEM +entity monster_info[MON_MAXCOUNT]; +entity dummy_monster_info; + +void register_monster(float id, float(float) func, float monstertype, vector min_s, vector max_s, string modelname, string shortname, string mname) +{ + entity e; + monster_info[id - 1] = e = spawn(); + e.classname = "monster_info"; + e.monsterid = id; + e.netname = shortname; + e.monster_name = mname; + e.monster_func = func; + e.mdl = modelname; + e.mins = min_s; + e.maxs = max_s; + e.model = strzone(strcat("models/monsters/", modelname)); + e.spawnflags = monstertype; + + func(MR_INIT); +} +float m_null(float dummy) { return 0; } +void register_monsters_done() +{ + dummy_monster_info = spawn(); + dummy_monster_info.classname = "monster_info"; + dummy_monster_info.monsterid = 0; // you can recognize dummies by this + dummy_monster_info.netname = ""; + dummy_monster_info.monster_name = "Monster"; + dummy_monster_info.monster_func = m_null; + dummy_monster_info.mdl = ""; + dummy_monster_info.mins = '-0 -0 -0'; + dummy_monster_info.maxs = '0 0 0'; + dummy_monster_info.model = ""; + dummy_monster_info.spawnflags = 0; +} +entity get_monsterinfo(float id) +{ + entity m; + if(id < MON_FIRST || id > MON_LAST) + return dummy_monster_info; + m = monster_info[id - 1]; + if(m) + return m; + return dummy_monster_info; +} diff --git a/qcsrc/common/monsters/monsters.qh b/qcsrc/common/monsters/monsters.qh new file mode 100644 index 000000000..5124e4528 --- /dev/null +++ b/qcsrc/common/monsters/monsters.qh @@ -0,0 +1,87 @@ +// monster requests +#define MR_SETUP 1 // (SERVER) setup monster data +#define MR_THINK 2 // (SERVER) logic to run every frame +#define MR_DEATH 3 // (BOTH) called when monster dies +#define MR_INIT 4 // (BOTH) precaches models/sounds used by this monster +#define MR_CONFIG 5 // (ALL) + +// functions: +entity get_monsterinfo(float id); + +// entity properties of monsterinfo: +.float monsterid; // MON_... +.string netname; // short name +.string monster_name; // human readable name +.float(float) monster_func; // m_... +.string mdl; // currently a copy of the model +.string model; // full name of model +.float spawnflags; + +// other useful macros +#define MON_ACTION(monstertype,mrequest) (get_monsterinfo(monstertype)).monster_func(mrequest) +#define M_NAME(monstertype) (get_monsterinfo(monstertype)).monster_name + +// ===================== +// Monster Registration +// ===================== + +float m_null(float dummy); +void register_monster(float id, float(float) func, float monstertype, vector min_s, vector max_s, string modelname, string shortname, string mname); +void register_monsters_done(); + +// special spawn flags +const float MONSTER_RESPAWN_DEATHPOINT = 699; // re-spawn where we died +const float MONSTER_TYPE_FLY = 700; +const float MONSTER_TYPE_SWIM = 701; +const float MONSTER_SIZE_BROKEN = 702; // TODO: remove when bad models are replaced + +const float MON_MAXCOUNT = 24; +#define MON_FIRST 1 +float MON_COUNT; +float MON_LAST; + +#define REGISTER_MONSTER_2(id,func,monstertype,min_s,max_s,modelname,shortname,mname) \ + float id; \ + float func(float); \ + void RegisterMonsters_##id() \ + { \ + MON_LAST = (id = MON_FIRST + MON_COUNT); \ + ++MON_COUNT; \ + register_monster(id,func,monstertype,min_s,max_s,modelname,shortname,mname); \ + } \ + ACCUMULATE_FUNCTION(RegisterMonsters, RegisterMonsters_##id) +#define REGISTER_MONSTER(id,func,monstertype,min_s,max_s,modelname,shortname,mname) \ + REGISTER_MONSTER_2(MON_##id,func,monstertype,min_s,max_s,modelname,shortname,mname) + +#define MON_DUPECHECK(dupecheck,cvar) \ + #ifndef dupecheck \ + #define dupecheck \ + float cvar; \ + #else \ + #error DUPLICATE MONSTER CVAR: cvar \ + #endif + +#define MON_ADD_CVAR(monster,name) \ + MON_DUPECHECK(MON_CVAR_##monster##_##name, autocvar_g_monster_##monster##_##name) + +#define MON_CVAR(monster,name) autocvar_g_balance_##monster##_##name + +#define MON_ADD_PROP(monster,prop,name) \ + .float ##prop; \ + MON_DUPECHECK(MON_CVAR_##monster##_##name, autocvar_g_monster_##monster##_##name) + +#define MON_SET_PROP(wepid,monster,prop,name) get_monsterinfo(##wepid).##prop = autocvar_g_monster_##monster##_##name; + +#define MON_SET_PROPS(monsettings,wepid) \ + #define MON_ADD_CVAR(monster,mode,name) \ + #define MON_ADD_PROP(monster,prop,name) MON_SET_PROP(wepid,monster,prop,name) \ + monsettings \ + #undef MON_ADD_CVAR \ + #undef MON_ADD_PROP + +#include "all.qh" + +#undef MON_ADD_CVAR +#undef MON_ADD_PROP +#undef REGISTER_MONSTER +ACCUMULATE_FUNCTION(RegisterMonsters, register_monsters_done) diff --git a/qcsrc/server/command/cmd.qc b/qcsrc/server/command/cmd.qc index a12fc3f14..ce049adf3 100644 --- a/qcsrc/server/command/cmd.qc +++ b/qcsrc/server/command/cmd.qc @@ -197,7 +197,7 @@ void ClientCommand_mobedit(float request, float argc) switch(argv(1)) { case "name": trace_ent.netname = strzone(strdecolorize(argv(2))); if(trace_ent.sprite) WaypointSprite_UpdateSprites(trace_ent.sprite, trace_ent.netname, "", ""); return; - case "skin": if(trace_ent.monsterid != MONSTER_MAGE) { trace_ent.skin = stof(argv(2)); trace_ent.SendFlags |= MSF_STATUS; } return; + case "skin": if(trace_ent.monsterid != MON_MAGE) { trace_ent.skin = stof(argv(2)); trace_ent.SendFlags |= MSF_STATUS; } return; case "movetarget": trace_ent.monster_moveflags = stof(argv(2)); return; } } @@ -268,8 +268,8 @@ void ClientCommand_mobspawn(float request, float argc) float i; string list = "Available monsters:"; - for(i = MONSTER_FIRST + 1; i < MONSTER_LAST; ++i) - list = strcat(list, " ", monster_id2string(i)); + for(i = MON_FIRST; i <= MON_LAST; ++i) + list = strcat(list, " ", (get_monsterinfo(i)).netname); sprint(self, strcat(list, "\n")); @@ -295,9 +295,9 @@ void ClientCommand_mobspawn(float request, float argc) //WarpZone_TraceLine(self.origin + self.view_ofs, self.origin + self.view_ofs + v_forward * 150, MOVE_NORMAL, self); e = spawnmonster(tospawn, 0, self, self, trace_endpos, FALSE, moveflag); - if(mname) e.netname = strzone(mname); + if(mname) e.monster_name = strzone(mname); - sprint(self, strcat("Spawned ", e.netname, "\n")); + sprint(self, strcat("Spawned ", e.monster_name, "\n")); } return; diff --git a/qcsrc/server/command/sv_cmd.qc b/qcsrc/server/command/sv_cmd.qc index 34a8a84f5..2175ceb42 100644 --- a/qcsrc/server/command/sv_cmd.qc +++ b/qcsrc/server/command/sv_cmd.qc @@ -146,6 +146,7 @@ void GameCommand_butcher(float request) case CMD_REQUEST_COMMAND: { if(autocvar_g_campaign) { print("This command doesn't work in campaign mode.\n"); return; } + if(g_invasion) { print("This command doesn't work during an invasion.\n"); return; } float removed_count = 0; entity montokill, head; diff --git a/qcsrc/server/g_damage.qc b/qcsrc/server/g_damage.qc index ecc9c9e50..2e100f2ce 100644 --- a/qcsrc/server/g_damage.qc +++ b/qcsrc/server/g_damage.qc @@ -734,7 +734,7 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, float mirrorforce *= g_weaponforcefactor; } - if(((targ.frozen == 2 && attacker.monsterid != MONSTER_SPIDER) || (targ.frozen == 1)) && deathtype != DEATH_HURTTRIGGER) + if(((targ.frozen == 2 && attacker.monsterid != MON_SPIDER) || (targ.frozen == 1)) && deathtype != DEATH_HURTTRIGGER) { damage = 0; force *= 0.2; diff --git a/qcsrc/server/g_world.qc b/qcsrc/server/g_world.qc index 23f51f54a..c9e24123a 100644 --- a/qcsrc/server/g_world.qc +++ b/qcsrc/server/g_world.qc @@ -546,6 +546,7 @@ void spawnfunc___init_dedicated_server(void) // needs to be done so early because of the constants they create CALL_ACCUMULATED_FUNCTION(RegisterWeapons); + CALL_ACCUMULATED_FUNCTION(RegisterMonsters); CALL_ACCUMULATED_FUNCTION(RegisterGametypes); CALL_ACCUMULATED_FUNCTION(RegisterNotifications); CALL_ACCUMULATED_FUNCTION(RegisterDeathtypes); @@ -594,6 +595,7 @@ void spawnfunc_worldspawn (void) // needs to be done so early because of the constants they create CALL_ACCUMULATED_FUNCTION(RegisterWeapons); + CALL_ACCUMULATED_FUNCTION(RegisterMonsters); CALL_ACCUMULATED_FUNCTION(RegisterGametypes); CALL_ACCUMULATED_FUNCTION(RegisterNotifications); CALL_ACCUMULATED_FUNCTION(RegisterDeathtypes); diff --git a/qcsrc/server/monsters/lib/defs.qh b/qcsrc/server/monsters/lib/defs.qh deleted file mode 100644 index f5a75e3c8..000000000 --- a/qcsrc/server/monsters/lib/defs.qh +++ /dev/null @@ -1,52 +0,0 @@ -.float(float attack_type) monster_attackfunc; -const float MONSTER_ATTACK_MELEE = 1; -const float MONSTER_ATTACK_RANGED = 2; - -.float candrop; - -.float attack_range; - -.float spawn_time; // stop monster from moving around right after spawning - -.string oldtarget2; -.float lastshielded; - -const float MONSTER_RESPAWN_DEATHPOINT = 8; // re-spawn where we died - -.float monster_respawned; // used to make sure we're not recounting respawned monster stats - -float monsters_spawned; - -const float MONSTERSKILL_NOTEASY = 256; // monster will not spawn on skill <= 2 -const float MONSTERSKILL_NOTMEDIUM = 512; // monster will not spawn on skill 3 -const float MONSTERSKILL_NOTHARD = 1024; // monster will not spawn on skill 4 -const float MONSTERSKILL_NOTINSANE = 2048; // monster will not spawn on skill 5 -const float MONSTERSKILL_NOTNIGHTMARE = 4096; // monster will not spawn on skill >= 6 - -// new flags -const float MONSTERFLAG_MINIBOSS = 1; // monster spawns as mini-boss (also has a chance of naturally becoming one) -const float MONSTERFLAG_APPEAR = 2; // delay spawn until triggered -const float MONSTERFLAG_NORESPAWN = 4; -const float MONSTERFLAG_SPAWNED = 512; // flag for spawned monsters - -.float msound_delay; // restricts some monster sounds -.string msound_idle; -.string msound_death; -.string msound_attack_melee; -.string msound_attack_ranged; -.string msound_spawn; -.string msound_sight; -.string msound_pain; - -.void() monster_spawnfunc; -.void() monster_die; - -.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 - -const float MONSTER_STATE_ATTACK_LEAP = 1; -const float MONSTER_STATE_ATTACK_MELEE = 2; diff --git a/qcsrc/server/monsters/lib/monsters.qc b/qcsrc/server/monsters/lib/monsters.qc deleted file mode 100644 index 7e013515a..000000000 --- a/qcsrc/server/monsters/lib/monsters.qc +++ /dev/null @@ -1,1048 +0,0 @@ -// TODO: clean up this file? - -void() spawnfunc_item_minst_cells; - -void M_Item_Touch () -{ - if(self && IS_PLAYER(other) && other.deadflag == DEAD_NO) - { - Item_Touch(); - self.think = SUB_Remove; - self.nextthink = time + 0.1; - } -} - -void monster_item_spawn() -{ - if(self.monster_loot) - self.monster_loot(); - - self.gravity = 1; - self.velocity = randomvec() * 175 + '0 0 325'; - self.touch = M_Item_Touch; - - SUB_SetFade(self, time + autocvar_g_monsters_drop_time, 1); -} - -void Monster_DropItem (string itype, string itemsize) -{ - vector org = self.origin + ((self.mins + self.maxs) * 0.5); - entity e = spawn(); - - setorigin(e, org); - - switch(itype) - { - case "armor": - { - switch(itemsize) - { - case "mega": e.monster_loot = spawnfunc_item_armor_large; break; - case "large": e.monster_loot = spawnfunc_item_armor_big; break; - case "medium": e.monster_loot = spawnfunc_item_armor_medium; break; - case "small": e.monster_loot = spawnfunc_item_armor_small; break; - } - break; - } - case "health": - { - switch(itemsize) - { - case "mega": e.monster_loot = spawnfunc_item_health_mega; break; - case "large": e.monster_loot = spawnfunc_item_health_large; break; - case "medium": e.monster_loot = spawnfunc_item_health_medium; break; - case "small": e.monster_loot = spawnfunc_item_health_small; break; - } - break; - } - case "ammo": - { - switch(itemsize) - { - case "shells": e.monster_loot = spawnfunc_item_shells; break; - case "cells": e.monster_loot = spawnfunc_item_cells; break; - case "rockets": e.monster_loot = spawnfunc_item_rockets; break; - case "bullets": - case "nails": e.monster_loot = spawnfunc_item_bullets; break; - } - break; - } - } - - other = e; - MUTATOR_CALLHOOK(MonsterDropItem); - e = other; - - e.think = monster_item_spawn; - e.nextthink = time + 0.1; -} - -void monsters_setframe(float _frame) -{ - if(self.frame == _frame) - return; - - self.anim_start_time = time; - self.frame = _frame; - self.SendFlags |= MSF_ANIM; -} - -float monster_isvalidtarget (entity targ, entity ent) -{ - if(!targ || !ent) - return FALSE; // someone doesn't exist - - if(time < game_starttime) - return FALSE; // monsters do nothing before the match has started - - WarpZone_TraceLine(ent.origin, targ.origin, MOVE_NORMAL, ent); - - if(vlen(targ.origin - ent.origin) >= ent.target_range) - return FALSE; // enemy is too far away - - if not(targ.vehicle_flags & VHF_ISVEHICLE) - if(trace_ent != targ) - return FALSE; // we can't see the enemy - - if(targ.takedamage == DAMAGE_NO) - return FALSE; // enemy can't be damaged - - if(targ.items & IT_INVISIBILITY) - return FALSE; // enemy is invisible - - if(substring(targ.classname, 0, 10) == "onslaught_") - return FALSE; // don't attack onslaught targets - - if(IS_SPEC(targ) || IS_OBSERVER(targ)) - return FALSE; // enemy is a spectator - - if not(targ.vehicle_flags & VHF_ISVEHICLE) // vehicles dont count as alive? - if(targ.deadflag != DEAD_NO || ent.deadflag != DEAD_NO || targ.health <= 0 || ent.health <= 0) - return FALSE; // enemy/self is dead - - if(targ.monster_owner == ent || ent.monster_owner == targ) - return FALSE; // enemy owns us, or we own them - - if not(targ.vehicle_flags & VHF_ISVEHICLE) - if(targ.flags & FL_NOTARGET) - return FALSE; // enemy can't be targeted - - if not(autocvar_g_monsters_typefrag) - if(targ.BUTTON_CHAT) - return FALSE; // no typefragging! - - if not(IsDifferentTeam(targ, ent)) - return FALSE; // enemy is on our team - - if(autocvar_g_monsters_target_infront) - if(ent.enemy != targ) - { - float dot; - - makevectors (ent.angles); - dot = normalize (targ.origin - ent.origin) * v_forward; - - if(dot <= 0.3) - return FALSE; - } - - return TRUE; -} - -entity FindTarget (entity ent) -{ - if(MUTATOR_CALLHOOK(MonsterFindTarget)) { return ent.enemy; } // Handled by a mutator - entity e; - - for(e = world; (e = findflags(e, monster_attack, TRUE)); ) - if(monster_isvalidtarget(e, ent)) - return e; - - return world; -} - -void MonsterTouch () -{ - if(other == world) - return; - - if(self.enemy != other) - if not(other.flags & FL_MONSTER) - if(monster_isvalidtarget(other, self)) - self.enemy = other; -} - -void monster_sound(string msound, float sound_delay, float delaytoo) -{ - if(delaytoo && time < self.msound_delay) - return; // too early - - if(msound == "") - return; // sound doesn't exist - - sound(self, CHAN_AUTO, msound, VOL_BASE, ATTN_NORM); - - self.msound_delay = time + sound_delay; -} - -void monster_precachesounds(entity e) -{ - precache_sound(e.msound_idle); - precache_sound(e.msound_death); - precache_sound(e.msound_attack_melee); - precache_sound(e.msound_attack_ranged); - precache_sound(e.msound_sight); - precache_sound(e.msound_pain); -} - -void monster_setupsounds(string mon) -{ - if(self.msound_idle == "") self.msound_idle = strzone(strcat("monsters/", mon, "_idle.wav")); - if(self.msound_death == "") self.msound_death = strzone(strcat("monsters/", mon, "_death.wav")); - if(self.msound_pain == "") self.msound_pain = strzone(strcat("monsters/", mon, "_pain.wav")); - if(self.msound_attack_melee == "") self.msound_attack_melee = strzone(strcat("monsters/", mon, "_melee.wav")); - if(self.msound_attack_ranged == "") self.msound_attack_ranged = strzone(strcat("monsters/", mon, "_attack.wav")); - if(self.msound_sight == "") self.msound_sight = strzone(strcat("monsters/", mon, "_sight.wav")); -} - -float monster_melee (entity targ, float damg, float er, float deathtype, float dostop) -{ - float dot, rdmg = damg * random(); - - if (self.health <= 0) - return FALSE; - if (targ == world) - return FALSE; - - if(dostop) - { - self.velocity_x = 0; - self.velocity_y = 0; - self.state = MONSTER_STATE_ATTACK_MELEE; - self.SendFlags |= MSF_MOVE; - } - - makevectors (self.angles); - dot = normalize (targ.origin - self.origin) * v_forward; - - if(dot > er) - Damage(targ, self, self, rdmg * monster_skill, deathtype, targ.origin, normalize(targ.origin - self.origin)); - - return TRUE; -} - -void Monster_CheckDropCvars (string mon) -{ - if not(self.candrop) - return; // forced off - - string dropitem; - string dropsize; - - dropitem = cvar_string(strcat("g_monster_", mon, "_drop")); - dropsize = cvar_string(strcat("g_monster_", mon, "_drop_size")); - - if(autocvar_g_monsters_forcedrop) - Monster_DropItem(autocvar_g_monsters_drop_type, autocvar_g_monsters_drop_size); - else if(dropitem != "") - Monster_DropItem(dropitem, dropsize); - else - Monster_DropItem("armor", "medium"); -} - -void Monster_CheckMinibossFlag () -{ - if(MUTATOR_CALLHOOK(MonsterCheckBossFlag)) - return; - - float chance = random() * 100; - - // g_monsters_miniboss_chance cvar or spawnflags 64 causes a monster to be a miniboss - if ((self.spawnflags & MONSTERFLAG_MINIBOSS) || (chance < autocvar_g_monsters_miniboss_chance)) - { - self.health += autocvar_g_monsters_miniboss_healthboost; - self.flags |= MONSTERFLAG_MINIBOSS; - if not(self.weapon) - self.weapon = WEP_NEX; - } -} - -float Monster_CanRespawn(entity ent) -{ - other = ent; - if(MUTATOR_CALLHOOK(MonsterRespawn)) - return TRUE; // enabled by a mutator - - if(ent.spawnflags & MONSTERFLAG_NORESPAWN) - return FALSE; - - if not(autocvar_g_monsters_respawn) - return FALSE; - - return TRUE; -} - -void Monster_Fade () -{ - if(Monster_CanRespawn(self)) - { - self.monster_respawned = TRUE; - self.think = self.monster_spawnfunc; - self.nextthink = time + self.respawntime; - self.deadflag = DEAD_RESPAWNING; - if(self.spawnflags & MONSTER_RESPAWN_DEATHPOINT) - { - self.pos1 = self.origin; - self.pos2 = self.angles; - } - self.event_damage = func_null; - self.takedamage = DAMAGE_NO; - setorigin(self, self.pos1); - self.angles = self.pos2; - self.health = self.max_health; // TODO: check if resetting to max_health is wise here - - self.SendFlags |= MSF_MOVE; - self.SendFlags |= MSF_STATUS; - - return; - } - SUB_SetFade(self, time + 3, 1); -} - -float Monster_CanJump (vector vel) -{ - if(self.state) - return FALSE; // already attacking - if not(self.flags & FL_ONGROUND) - return FALSE; // not on the ground - if(self.health <= 0) - return FALSE; // called when dead? - if(time < self.attack_finished_single) - return FALSE; // still attacking - - vector old = self.velocity; - - self.velocity = vel; - tracetoss(self, self); - self.velocity = old; - if (trace_ent != self.enemy) - return FALSE; - - return TRUE; -} - -float monster_leap (float anm, void() touchfunc, vector vel, float anim_finished) -{ - if(!Monster_CanJump(vel)) - return FALSE; - - monsters_setframe(anm); - self.state = MONSTER_STATE_ATTACK_LEAP; - self.touch = touchfunc; - self.origin_z += 1; - self.velocity = vel; - self.flags &~= FL_ONGROUND; - - self.attack_finished_single = time + anim_finished; - - return TRUE; -} - -void monster_checkattack(entity e, entity targ) -{ - if(e == world) - return; - if(targ == world) - return; - - if not(e.monster_attackfunc) - return; - - if(time < e.attack_finished_single) - return; - - if(vlen(targ.origin - e.origin) <= e.attack_range) - if(e.monster_attackfunc(MONSTER_ATTACK_MELEE)) - { - monster_sound(e.msound_attack_melee, 0, FALSE); - return; - } - - if(e.monster_attackfunc(MONSTER_ATTACK_RANGED)) - { - monster_sound(e.msound_attack_ranged, 0, FALSE); - return; - } -} - -void monster_makevectors(entity e) -{ - vector v; - - v = CENTER_OR_VIEWOFS(e); - self.v_angle = vectoangles(v - (self.origin + self.view_ofs)); - self.v_angle_x = -self.v_angle_x; - - makevectors(self.v_angle); -} - -void monster_use () -{ - if (self.enemy) - return; - if (self.health <= 0) - return; - - if(!monster_isvalidtarget(activator, self)) - return; - - self.enemy = activator; -} - -float trace_path(vector from, vector to) -{ - vector dir = normalize(to - from) * 15, offset = '0 0 0'; - float trace1 = trace_fraction; - - offset_x = dir_y; - offset_y = -dir_x; - traceline (from+offset, to+offset, TRUE, self); - - traceline(from-offset, to-offset, TRUE, self); - - return ((trace1 < trace_fraction) ? trace1 : trace_fraction); -} - -.float last_trace; -.float last_enemycheck; // for checking enemy -vector monster_pickmovetarget(entity targ) -{ - // enemy is always preferred target - if(self.enemy) - { - self.monster_movestate = MONSTER_MOVE_ENEMY; - self.last_trace = time + 1.2; - return self.enemy.origin; - } - - switch(self.monster_moveflags) - { - case MONSTER_MOVE_OWNER: - { - self.monster_movestate = MONSTER_MOVE_OWNER; - self.last_trace = time + 0.3; - if(self.monster_owner && self.monster_owner.classname != "td_spawnpoint") - return self.monster_owner.origin; - } - case MONSTER_MOVE_SPAWNLOC: - { - self.monster_movestate = MONSTER_MOVE_SPAWNLOC; - self.last_trace = time + 2; - return self.pos1; - } - case MONSTER_MOVE_NOMOVE: - { - self.monster_movestate = MONSTER_MOVE_NOMOVE; - self.last_trace = time + 2; - return self.origin; - } - default: - case MONSTER_MOVE_WANDER: - { - vector pos; - self.monster_movestate = MONSTER_MOVE_WANDER; - self.last_trace = time + 2; - - self.angles_y = random() * 500; - makevectors(self.angles); - pos = self.origin + v_forward * 600; - - if(self.flags & FL_FLY || self.flags & FL_SWIM) - { - pos_z = random() * 200; - if(random() >= 0.5) - pos_z *= -1; - } - - if(targ) - { - self.last_trace = time + 0.5; - pos = targ.origin; - } - - return pos; - } - } -} - -void monster_move(float runspeed, float walkspeed, float stopspeed, float manim_run, float manim_walk, float manim_idle) -{ - fixedmakevectors(self.angles); - - if(self.target2) - self.goalentity = find(world, targetname, self.target2); - - entity targ; - - if(self.frozen) - { - self.revive_progress = bound(0, self.revive_progress + frametime * self.revive_speed, 1); - self.health = max(1, self.max_health * self.revive_progress); - - if(self.sprite) WaypointSprite_UpdateHealth(self.sprite, self.health); - - movelib_beak_simple(stopspeed); - - self.velocity = '0 0 0'; - self.enemy = world; - self.nextthink = time + 0.1; - - if(self.revive_progress >= 1) - Unfreeze(self); // wait for next think before attacking - - self.SendFlags |= MSF_MOVE; - - return; // no moving while frozen - } - - if(self.flags & FL_SWIM) - { - if(self.waterlevel < WATERLEVEL_WETFEET) - { - if(time >= self.last_trace) - { - self.last_trace = time + 0.4; - - Damage (self, world, world, 2, DEATH_DROWN, self.origin, '0 0 0'); - self.angles = '90 90 0'; - if(random() < 0.5) - { - self.velocity_y += random() * 50; - self.velocity_x -= random() * 50; - } - else - { - self.velocity_y -= random() * 50; - self.velocity_x += random() * 50; - } - self.velocity_z += random() * 150; - } - - - self.movetype = MOVETYPE_BOUNCE; - //self.velocity_z = -200; - - self.SendFlags |= MSF_MOVE | MSF_ANG; - - return; - } - else - { - self.angles = '0 0 0'; - self.movetype = MOVETYPE_WALK; - } - } - - targ = self.goalentity; - - monster_target = targ; - monster_speed_run = runspeed; - monster_speed_walk = walkspeed; - - if(MUTATOR_CALLHOOK(MonsterMove) || gameover || (round_handler_IsActive() && !round_handler_IsRoundStarted()) || time < game_starttime || (autocvar_g_campaign && !campaign_bots_may_start) || time < self.spawn_time) - { - runspeed = walkspeed = 0; - if(time >= self.spawn_time) - monsters_setframe(manim_idle); - movelib_beak_simple(stopspeed); - self.SendFlags |= MSF_MOVE; - return; - } - - targ = monster_target; - runspeed = monster_speed_run; - walkspeed = monster_speed_walk; - - if(IsDifferentTeam(self.monster_owner, self)) - self.monster_owner = world; - - if(time >= self.last_enemycheck) - { - if not(monster_isvalidtarget(self.enemy, self)) - self.enemy = world; - self.last_enemycheck = time + 2; - } - - if(self.enemy && self.enemy.health < 1) - self.enemy = world; // enough! - - if not(self.enemy) - { - self.enemy = FindTarget(self); - if(self.enemy) - monster_sound(self.msound_sight, 0, FALSE); - } - - if(self.state == MONSTER_STATE_ATTACK_MELEE && time >= self.attack_finished_single) - self.state = 0; - - if(self.state != MONSTER_STATE_ATTACK_MELEE) // don't move if set - if(time >= self.last_trace || self.enemy) // update enemy instantly - self.moveto = monster_pickmovetarget(targ); - - if not(self.enemy) - monster_sound(self.msound_idle, 5, TRUE); - - if(self.state != MONSTER_STATE_ATTACK_LEAP && self.state != MONSTER_STATE_ATTACK_MELEE) - self.steerto = steerlib_attract2(self.moveto, 0.5, 500, 0.95); - - if(self.state == MONSTER_STATE_ATTACK_LEAP && (self.flags & FL_ONGROUND)) - { - self.state = 0; - self.touch = MonsterTouch; - } - - //self.steerto = steerlib_attract2(self.moveto, 0.5, 500, 0.95); - - float turny = 0; - vector real_angle = vectoangles(self.steerto) - self.angles; - - if(self.state != MONSTER_STATE_ATTACK_LEAP && self.state != MONSTER_STATE_ATTACK_MELEE) - turny = 20; - - if(self.flags & FL_SWIM) - turny = vlen(self.angles - self.moveto); - - if(turny) - { - turny = bound(turny * -1, shortangle_f(real_angle_y, self.angles_y), turny); - self.angles_y += turny; - } - - if(self.state == MONSTER_STATE_ATTACK_MELEE) - self.moveto = self.origin; - else if(self.enemy) - self.moveto = self.moveto * 0.9 + ((self.origin + v_forward * 500) + randomvec() * 400) * 0.1; - - if not(self.flags & FL_FLY || self.flags & FL_SWIM) - self.moveto_z = self.origin_z; - - float l = vlen(self.moveto - self.origin); - float t1 = trace_path(self.origin+'0 0 10', self.moveto+'0 0 10'); - float t2 = trace_path(self.origin-'0 0 15', self.moveto-'0 0 15'); - - if(self.flags & FL_FLY || self.flags & FL_SWIM) - v_forward = normalize(self.moveto - self.origin); - - if(t1*l-t2*l>50 && (t1*l > 100 || t1 > 0.8)) - if(self.flags & FL_ONGROUND) - movelib_jump_simple(100); - - if(vlen(self.origin - self.moveto) > 64) - { - if(self.flags & FL_FLY || self.flags & FL_SWIM) - 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) - monsters_setframe((self.enemy) ? manim_run : manim_walk); - } - else - { - entity e = find(world, targetname, self.target2); - if(e.target2) - self.target2 = e.target2; - else if(e.target) - self.target2 = e.target; - - movelib_beak_simple(stopspeed); - if(time > self.attack_finished_single) - if(time > self.pain_finished) - if (vlen(self.velocity) <= 30) - monsters_setframe(manim_idle); - } - - monster_checkattack(self, self.enemy); - - self.SendFlags |= MSF_ANG; - self.SendFlags |= MSF_MOVE; -} - -void monster_dead_think() -{ - self.think = monster_dead_think; - self.nextthink = time + 0.3; // don't need to update so often now - - self.deadflag = DEAD_DEAD; - - if(time >= self.ltime) - { - Monster_Fade(); - return; - } - - self.SendFlags |= MSF_MOVE; // keep up to date on the monster's location -} - -void monsters_setstatus() -{ - self.stat_monsters_total = monsters_total; - self.stat_monsters_killed = monsters_killed; -} - -void Monster_Appear() -{ - self.enemy = activator; - self.spawnflags &~= MONSTERFLAG_APPEAR; - self.monster_spawnfunc(); -} - -float Monster_CheckAppearFlags(entity ent) -{ - if not(ent.spawnflags & MONSTERFLAG_APPEAR) - return FALSE; - - ent.think = func_null; - ent.nextthink = 0; - ent.use = Monster_Appear; - ent.flags = FL_MONSTER; // set so this monster can get butchered - - return TRUE; -} - -void monsters_reset() -{ - setorigin(self, self.pos1); - self.angles = self.pos2; - - self.health = self.max_health; - self.velocity = '0 0 0'; - self.enemy = world; - self.goalentity = world; - self.attack_finished_single = 0; - self.moveto = self.origin; - - WaypointSprite_UpdateHealth(self.sprite, self.health); -} - -float monster_send(entity to, float sf) -{ - WriteByte(MSG_ENTITY, ENT_CLIENT_MONSTER); - WriteByte(MSG_ENTITY, sf); - if(sf & MSF_SETUP) - { - WriteByte(MSG_ENTITY, self.monsterid); - - WriteCoord(MSG_ENTITY, self.origin_x); - WriteCoord(MSG_ENTITY, self.origin_y); - WriteCoord(MSG_ENTITY, self.origin_z); - - WriteAngle(MSG_ENTITY, self.angles_x); - WriteAngle(MSG_ENTITY, self.angles_y); - - WriteByte(MSG_ENTITY, self.skin); - WriteByte(MSG_ENTITY, self.team); - } - - if(sf & MSF_ANG) - { - WriteShort(MSG_ENTITY, rint(self.angles_x)); - WriteShort(MSG_ENTITY, rint(self.angles_y)); - } - - if(sf & MSF_MOVE) - { - WriteShort(MSG_ENTITY, rint(self.origin_x)); - WriteShort(MSG_ENTITY, rint(self.origin_y)); - WriteShort(MSG_ENTITY, rint(self.origin_z)); - - WriteShort(MSG_ENTITY, rint(self.velocity_x)); - WriteShort(MSG_ENTITY, rint(self.velocity_y)); - WriteShort(MSG_ENTITY, rint(self.velocity_z)); - - WriteShort(MSG_ENTITY, rint(self.angles_y)); - } - - if(sf & MSF_ANIM) - { - WriteCoord(MSG_ENTITY, self.anim_start_time); - WriteByte(MSG_ENTITY, self.frame); - } - - if(sf & MSF_STATUS) - { - WriteByte(MSG_ENTITY, self.skin); - - WriteByte(MSG_ENTITY, self.team); - - WriteByte(MSG_ENTITY, self.deadflag); - - if(self.health <= 0) - WriteByte(MSG_ENTITY, 0); - else - WriteByte(MSG_ENTITY, ceil((self.health / self.max_health) * 255)); - } - - return TRUE; -} - -void monster_link(void() spawnproc) -{ - Net_LinkEntity(self, TRUE, 0, monster_send); - self.think = spawnproc; - self.nextthink = time; -} - -void monsters_corpse_damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) -{ - self.health -= damage; - - Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, self, attacker); - - if(self.health <= -100) // 100 health until gone? - { - Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, self, attacker); - - self.think = SUB_Remove; - self.nextthink = time + 0.1; - } -} - -void monsters_damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) -{ - if(self.frozen && deathtype != DEATH_KILL) - return; - - if(time < self.pain_finished && deathtype != DEATH_KILL) - return; - - if(time < self.spawnshieldtime) - return; - - if(deathtype != DEATH_KILL) - damage *= self.armorvalue; - - if(self.weaponentity && self.weaponentity.classname == "shield") - self.weaponentity.health -= damage; - - self.health -= damage; - - if(self.sprite) - WaypointSprite_UpdateHealth(self.sprite, self.health); - - self.dmg_time = time; - - if(sound_allowed(MSG_BROADCAST, attacker) && deathtype != DEATH_DROWN) - spamsound (self, CH_PAIN, "misc/bodyimpact1.wav", VOL_BASE, ATTN_NORM); // FIXME: PLACEHOLDER - - self.velocity += force * self.damageforcescale; - - if(deathtype != DEATH_DROWN) - { - 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(self.health <= 0) - { - if(self.sprite) - { - // Update one more time to avoid waypoint fading without emptying healthbar - WaypointSprite_UpdateHealth(self.sprite, 0); - } - - if(deathtype == DEATH_KILL) - self.candrop = FALSE; // killed by mobkill command - - // TODO: fix this? - activator = attacker; - other = self.enemy; - SUB_UseTargets(); - self.target2 = self.oldtarget2; // reset to original target on death, incase we respawn - - self.monster_die(); - - frag_attacker = attacker; - frag_target = self; - MUTATOR_CALLHOOK(MonsterDies); - - if(self.health <= -100) // check if we're already gibbed - { - Violence_GibSplash(self, 1, 0.5, attacker); - - self.think = SUB_Remove; - self.nextthink = time + 0.1; - } - } - - self.SendFlags |= MSF_STATUS; -} - -// post-death functions -void monster_hook_death() -{ - WaypointSprite_Kill(self.sprite); - - if(self.weaponentity) - { - remove(self.weaponentity); - self.weaponentity = world; - } - - monster_sound(self.msound_death, 0, FALSE); - - if(!(self.spawnflags & MONSTERFLAG_SPAWNED) && !self.monster_respawned) - monsters_killed += 1; - - if(self.candrop && self.weapon) - W_ThrowNewWeapon(self, self.weapon, 0, self.origin, randomvec() * 150 + '0 0 325'); - - if(IS_CLIENT(self.realowner)) - self.realowner.monstercount -= 1; - - self.event_damage = monsters_corpse_damage; - self.solid = SOLID_CORPSE; - self.takedamage = DAMAGE_AIM; - self.enemy = world; - self.movetype = MOVETYPE_TOSS; - self.moveto = self.origin; - self.touch = MonsterTouch; // reset incase monster was pouncing - - if not(self.flags & FL_FLY) - self.velocity = '0 0 0'; - - self.SendFlags |= MSF_MOVE; - - totalspawned -= 1; -} - -// post-spawn functions -void monster_hook_spawn() -{ - if not(self.monster_respawned) - Monster_CheckMinibossFlag(); - - self.max_health = self.health; - self.pain_finished = self.nextthink; - self.anim_start_time = time; - - if not(self.noalign) - { - setorigin(self, self.origin + '0 0 20'); - tracebox(self.origin + '0 0 100', self.mins, self.maxs, self.origin - '0 0 10000', MOVE_WORLDONLY, self); - setorigin(self, trace_endpos); - } - - if not(self.monster_respawned) - if not(self.skin) - self.skin = rint(random() * 4); - - self.pos1 = self.origin; - - monster_precachesounds(self); - - if(teamplay) - self.monster_attack = TRUE; // we can have monster enemies in team games - - if(autocvar_g_monsters_healthbars) - { - WaypointSprite_Spawn(strzone(strdecolorize(self.netname)), 0, 600, self, '0 0 1' * (self.maxs_z + 15), world, 0, self, sprite, FALSE, RADARICON_DANGER, ((self.team) ? Team_ColorRGB(self.team) : '1 0 0')); - WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health); - WaypointSprite_UpdateHealth(self.sprite, self.health); - } - - monster_sound(self.msound_spawn, 0, FALSE); - - MUTATOR_CALLHOOK(MonsterSpawn); - - self.SendFlags = MSF_SETUP; -} - -float monster_initialize(string net_name, float mon_id, - vector min_s, - vector max_s, - float nodrop, - void() dieproc, - void() spawnproc) -{ - if not(autocvar_g_monsters) - return FALSE; - - // support for quake style removing monsters based on skill - if(monster_skill <= autocvar_g_monsters_skill_easy && (self.spawnflags & MONSTERSKILL_NOTEASY)) { return FALSE; } - if(monster_skill == autocvar_g_monsters_skill_normal && (self.spawnflags & MONSTERSKILL_NOTMEDIUM)) { return FALSE; } - if(monster_skill == autocvar_g_monsters_skill_hard && (self.spawnflags & MONSTERSKILL_NOTHARD)) { return FALSE; } - if(monster_skill == autocvar_g_monsters_skill_insane && (self.spawnflags & MONSTERSKILL_NOTINSANE)) { return FALSE; } - if(monster_skill >= autocvar_g_monsters_skill_nightmare && (self.spawnflags & MONSTERSKILL_NOTNIGHTMARE)) { return FALSE; } - - if(self.netname == "") - self.netname = ((net_name == "") ? self.classname : net_name); - - if(self.team && !teamplay) - self.team = 0; - - self.flags = FL_MONSTER; - - if not(self.spawnflags & MONSTERFLAG_SPAWNED) // naturally spawned monster - if not(self.monster_respawned) - monsters_total += 1; - - setsize(self, min_s, max_s); - self.takedamage = DAMAGE_AIM; - self.bot_attack = TRUE; - self.iscreature = TRUE; - self.teleportable = TRUE; - self.damagedbycontents = TRUE; - self.monsterid = mon_id; - self.damageforcescale = 0.003; - self.monster_die = dieproc; - self.event_damage = monsters_damage; - self.touch = MonsterTouch; - self.use = monster_use; - self.solid = SOLID_BBOX; - self.scale = 1; - self.movetype = MOVETYPE_WALK; - self.spawnshieldtime = time + autocvar_g_monsters_spawnshieldtime; - monsters_spawned += 1; - self.enemy = world; - self.velocity = '0 0 0'; - self.moveto = self.origin; - self.pos2 = self.angles; - self.reset = monsters_reset; - self.candrop = TRUE; - self.view_ofs = '0 0 1' * (self.maxs_z * 0.5); - self.oldtarget2 = self.target2; - self.deadflag = DEAD_NO; // UNDEAD - self.noalign = nodrop; - self.spawn_time = time; - self.gravity = 1; - self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_BOTCLIP | DPCONTENTS_MONSTERCLIP; - - if not(self.attack_range) - self.attack_range = 120; - - if not(self.ticrate) - self.ticrate = autocvar_g_monsters_think_delay; - - self.ticrate = bound(sys_frametime, self.ticrate, 60); - - if not(self.armorvalue) - self.armorvalue = 1; // multiplier - - if not(self.target_range) - self.target_range = autocvar_g_monsters_target_range; - - if not(self.respawntime) - self.respawntime = autocvar_g_monsters_respawn_delay; - - if not(self.monster_moveflags) - self.monster_moveflags = MONSTER_MOVE_WANDER; - - monster_link(spawnproc); - - return TRUE; -} diff --git a/qcsrc/server/monsters/lib/monsters_early.qh b/qcsrc/server/monsters/lib/monsters_early.qh deleted file mode 100644 index 9c7fc1e75..000000000 --- a/qcsrc/server/monsters/lib/monsters_early.qh +++ /dev/null @@ -1,90 +0,0 @@ -// for definitions used outside the monsters folder - -#ifdef SVQC -.string spawnmob; -.float monster_attack; - -float monster_skill; -float spawncode_first_load; // used to tell the player the monster database is loading (TODO: fix this?) - -.entity monster_owner; // new monster owner entity, fixes non-solid monsters -.float monstercount; // per player monster count - -.float stat_monsters_killed; // stats -.float stat_monsters_total; -float monsters_total; -float monsters_killed; -void monsters_setstatus(); // monsters.qc -.float monster_moveflags; // checks where to move when not attacking -#endif // SVQC - -#ifndef MENUQC -.float monsterid; -// Monster IDs -float MONSTER_FIRST = 1; -float MONSTER_ZOMBIE = 2; -float MONSTER_BRUTE = 3; -float MONSTER_ANIMUS = 4; -float MONSTER_SHAMBLER = 5; -float MONSTER_BRUISER = 6; -float MONSTER_WYVERN = 7; -float MONSTER_CERBERUS = 8; -float MONSTER_SLIME = 9; -float MONSTER_KNIGHT = 10; -float MONSTER_STINGRAY = 11; -float MONSTER_MAGE = 12; -float MONSTER_SPIDER = 13; -float MONSTER_LAST = 14; - -// id-string converters (TODO: remove these!) -string monster_id2string(float mnster) -{ - switch(mnster) - { - case MONSTER_ZOMBIE: return "zombie"; - case MONSTER_BRUTE: return "brute"; - case MONSTER_ANIMUS: return "animus"; - case MONSTER_SHAMBLER: return "shambler"; - case MONSTER_BRUISER: return "bruiser"; - case MONSTER_WYVERN: return "wyvern"; - case MONSTER_CERBERUS: return "cerberus"; - case MONSTER_SLIME: return "slime"; - case MONSTER_KNIGHT: return "knight"; - case MONSTER_STINGRAY: return "stingray"; - case MONSTER_MAGE: return "mage"; - case MONSTER_SPIDER: return "spider"; - default: return ""; - } -} -float monster_string2id(string monster) -{ - switch(monster) - { - case "zombie": return MONSTER_ZOMBIE; - case "brute": return MONSTER_BRUTE; - case "animus": return MONSTER_ANIMUS; - case "shambler": return MONSTER_SHAMBLER; - case "bruiser": return MONSTER_BRUISER; - case "wyvern": return MONSTER_WYVERN; - case "cerberus": return MONSTER_CERBERUS; - case "slime": return MONSTER_SLIME; - case "knight": return MONSTER_KNIGHT; - case "stingray": return MONSTER_STINGRAY; - case "mage": return MONSTER_MAGE; - case "spider": return MONSTER_SPIDER; - default: return 0; - } -} - -.float anim_start_time; - -float MSF_UPDATE = 2; -float MSF_STATUS = 4; -float MSF_SETUP = 8; -float MSF_ANG = 16; -float MSF_MOVE = 32; -float MSF_ANIM = 64; - -float MSF_FULL_UPDATE = 16777215; - -#endif // CSQC/SVQC diff --git a/qcsrc/server/monsters/lib/spawn.qc b/qcsrc/server/monsters/lib/spawn.qc deleted file mode 100644 index b985c7f2c..000000000 --- a/qcsrc/server/monsters/lib/spawn.qc +++ /dev/null @@ -1,57 +0,0 @@ -entity spawnmonster (string monster, float mnster, entity spawnedby, entity own, vector orig, float respwn, float moveflag) -{ - if(!spawncode_first_load) - { - initialize_field_db(); - spawncode_first_load = TRUE; - } - - entity e = spawn(); - - e.spawnflags = MONSTERFLAG_SPAWNED; - - if not(respwn) - e.spawnflags |= MONSTERFLAG_NORESPAWN; - - setorigin(e, orig); - - if(monster != "") - if not(monster_string2id(monster)) - monster = "bruiser"; - - if(monster == "") - if(mnster) - monster = monster_id2string(mnster); - - e.realowner = spawnedby; - - if(moveflag) - e.monster_moveflags = moveflag; - - if (spawnedby.classname == "td_spawnpoint") - { - e.monster_owner = own; - e.team = spawnedby.team; - } - else if(IS_PLAYER(spawnedby)) - { - if(teamplay && autocvar_g_monsters_teams) - e.team = spawnedby.team; // colors handled in spawn code - - if(e.team) - e.colormap = 1024; - else - e.colormap = spawnedby.colormap; - - if(autocvar_g_monsters_owners) - e.monster_owner = own; // using .owner makes the monster non-solid for its master - - e.angles = spawnedby.angles; - } - - monster = strcat("$ spawnfunc_monster_", monster); - - target_spawn_edit_entity(e, monster, world, world, world, world, world); - - return e; -} diff --git a/qcsrc/server/monsters/monster/animus.qc b/qcsrc/server/monsters/monster/animus.qc deleted file mode 100644 index cfb2292b8..000000000 --- a/qcsrc/server/monsters/monster/animus.qc +++ /dev/null @@ -1,127 +0,0 @@ -const vector ANIMUS_MIN = '-41 -41 -31'; -const vector ANIMUS_MAX = '41 41 31'; - -string ANIMUS_MODEL = "models/monsters/demon.mdl"; - -#ifdef SVQC -float autocvar_g_monster_animus; -float autocvar_g_monster_animus_health; -float autocvar_g_monster_animus_attack_jump_damage; -float autocvar_g_monster_animus_damage; -float autocvar_g_monster_animus_speed_walk; -float autocvar_g_monster_animus_speed_run; - -const float animus_anim_stand = 0; -const float animus_anim_walk = 1; -const float animus_anim_run = 2; -const float animus_anim_leap = 3; -const float animus_anim_pain = 4; -const float animus_anim_death = 5; -const float animus_anim_attack = 6; - -void animus_think() -{ - self.think = animus_think; - self.nextthink = time + self.ticrate; - - monster_move(autocvar_g_monster_animus_speed_run, autocvar_g_monster_animus_speed_walk, 100, animus_anim_run, animus_anim_walk, animus_anim_stand); -} - -void animus_touch_jump() -{ - if (self.health <= 0) - return; - - if (monster_isvalidtarget(other, self)) - { - if (vlen(self.velocity) > 300) - { - Damage(other, self, self, autocvar_g_monster_animus_attack_jump_damage * monster_skill, DEATH_MONSTER_ANIMUS, other.origin, normalize(other.origin - self.origin)); - self.touch = MonsterTouch; // instantly turn it off to stop damage spam - } - } - - if(trace_dphitcontents) - self.touch = MonsterTouch; -} - -float animus_attack(float attack_type) -{ - switch(attack_type) - { - case MONSTER_ATTACK_MELEE: - { - monsters_setframe(animus_anim_attack); - self.attack_finished_single = time + 1; - monster_melee(self.enemy, autocvar_g_monster_animus_damage, 0.3, DEATH_MONSTER_ANIMUS, TRUE); - - return TRUE; - } - case MONSTER_ATTACK_RANGED: - { - makevectors(self.angles); - if(monster_leap(animus_anim_leap, animus_touch_jump, v_forward * 700 + '0 0 300', 0.8)) - return TRUE; - } - } - - return FALSE; -} - -void animus_die() -{ - Monster_CheckDropCvars ("animus"); - - self.think = monster_dead_think; - self.nextthink = time + self.ticrate; - self.ltime = time + 5; - monsters_setframe(animus_anim_death); - - monster_hook_death(); // for post-death mods -} - -void animus_spawn() -{ - if not(self.health) - self.health = autocvar_g_monster_animus_health; - - self.damageforcescale = 0; - self.classname = "monster_animus"; - self.monster_attackfunc = animus_attack; - self.nextthink = time + random() * 0.5 + 0.1; - self.think = animus_think; - - monsters_setframe(animus_anim_stand); - - monster_setupsounds("animus"); - - monster_hook_spawn(); // for post-spawn mods -} - -void spawnfunc_monster_animus() -{ - if not(autocvar_g_monster_animus) { remove(self); return; } - - self.monster_spawnfunc = spawnfunc_monster_animus; - - if(Monster_CheckAppearFlags(self)) - return; - - self.scale = 1.3; - - if not (monster_initialize( - "Animus", MONSTER_ANIMUS, - ANIMUS_MIN, ANIMUS_MAX, - FALSE, - animus_die, animus_spawn)) - { - remove(self); - return; - } -} - -// compatibility with old spawns -void spawnfunc_monster_demon1() { spawnfunc_monster_animus(); } -void spawnfunc_monster_demon() { spawnfunc_monster_animus(); } - -#endif // SVQC diff --git a/qcsrc/server/monsters/monster/bruiser.qc b/qcsrc/server/monsters/monster/bruiser.qc deleted file mode 100644 index e19066bba..000000000 --- a/qcsrc/server/monsters/monster/bruiser.qc +++ /dev/null @@ -1,105 +0,0 @@ -const vector BRUISER_MIN = '-20 -20 -31'; -const vector BRUISER_MAX = '20 20 53'; - -string BRUISER_MODEL = "models/monsters/knight.mdl"; - -#ifdef SVQC -float autocvar_g_monster_bruiser; -float autocvar_g_monster_bruiser_health; -float autocvar_g_monster_bruiser_melee_damage; -float autocvar_g_monster_bruiser_speed_walk; -float autocvar_g_monster_bruiser_speed_run; - -const float bruiser_anim_stand = 0; -const float bruiser_anim_run = 1; -const float bruiser_anim_runattack = 2; -const float bruiser_anim_pain1 = 3; -const float bruiser_anim_pain2 = 4; -const float bruiser_anim_attack = 5; -const float bruiser_anim_walk = 6; -const float bruiser_anim_kneel = 7; -const float bruiser_anim_standing = 8; -const float bruiser_anim_death1 = 9; -const float bruiser_anim_death2 = 10; - -void bruiser_think() -{ - self.think = bruiser_think; - self.nextthink = time + self.ticrate; - - monster_move(autocvar_g_monster_bruiser_speed_run, autocvar_g_monster_bruiser_speed_walk, 50, bruiser_anim_run, bruiser_anim_walk, bruiser_anim_stand); -} - -float bruiser_attack(float attack_type) -{ - switch(attack_type) - { - case MONSTER_ATTACK_MELEE: - { - float len = vlen(self.velocity); - monsters_setframe((len < 50) ? bruiser_anim_attack : bruiser_anim_runattack); - self.attack_finished_single = time + 1.25; - - monster_melee(self.enemy, autocvar_g_monster_bruiser_melee_damage, 0.3, DEATH_MONSTER_BRUISER, FALSE); - - return TRUE; - } - case MONSTER_ATTACK_RANGED: - } - - return FALSE; -} - -void bruiser_die() -{ - Monster_CheckDropCvars ("bruiser"); - - self.think = monster_dead_think; - self.nextthink = time + self.ticrate; - self.ltime = time + 5; - monsters_setframe((random() > 0.5) ? bruiser_anim_death1 : bruiser_anim_death2); - - monster_hook_death(); // for post-death mods -} - -void bruiser_spawn() -{ - if not(self.health) - self.health = autocvar_g_monster_bruiser_health; - - self.damageforcescale = 0.003; - self.classname = "monster_bruiser"; - self.monster_attackfunc = bruiser_attack; - self.nextthink = time + random() * 0.5 + 0.1; - self.think = bruiser_think; - - monsters_setframe(bruiser_anim_stand); - - monster_setupsounds("bruiser"); - - monster_hook_spawn(); // for post-spawn mods -} - -void spawnfunc_monster_bruiser() -{ - if not(autocvar_g_monster_bruiser) { remove(self); return; } - - self.monster_spawnfunc = spawnfunc_monster_bruiser; - - if(Monster_CheckAppearFlags(self)) - return; - - self.scale = 1.3; - - if not (monster_initialize( - "Bruiser", MONSTER_BRUISER, - BRUISER_MIN, BRUISER_MAX, - FALSE, - bruiser_die, bruiser_spawn)) - { - remove(self); - return; - } -} - -#endif // SVQC diff --git a/qcsrc/server/monsters/monster/brute.qc b/qcsrc/server/monsters/monster/brute.qc deleted file mode 100644 index 8952d5400..000000000 --- a/qcsrc/server/monsters/monster/brute.qc +++ /dev/null @@ -1,237 +0,0 @@ -const vector BRUTE_MIN = '-36 -36 -20'; -const vector BRUTE_MAX = '36 36 50'; - -string BRUTE_MODEL = "models/monsters/ogre.dpm"; - -#ifdef SVQC -float autocvar_g_monster_brute; -float autocvar_g_monster_brute_health; -float autocvar_g_monster_brute_chainsaw_damage; -float autocvar_g_monster_brute_speed_walk; -float autocvar_g_monster_brute_speed_run; -float autocvar_g_monster_brute_attack_uzi_bullets; -float autocvar_g_monster_brute_attack_uzi_damage; -float autocvar_g_monster_brute_attack_uzi_force; -float autocvar_g_monster_brute_attack_uzi_chance; -float autocvar_g_monster_brute_attack_grenade_damage; -float autocvar_g_monster_brute_attack_grenade_edgedamage; -float autocvar_g_monster_brute_attack_grenade_force; -float autocvar_g_monster_brute_attack_grenade_radius; - -const float brute_anim_idle = 0; -const float brute_anim_walk = 1; -const float brute_anim_run = 2; -const float brute_anim_pain = 3; -const float brute_anim_swing = 4; -const float brute_anim_die = 5; - -.float brute_cycles; - -void brute_think() -{ - self.think = brute_think; - self.nextthink = time + self.ticrate; - - monster_move(autocvar_g_monster_brute_speed_run, autocvar_g_monster_brute_speed_walk, 300, brute_anim_run, brute_anim_walk, brute_anim_idle); -} - -void brute_blade() -{ - self.brute_cycles += 1; - self.angles_y = self.angles_y + random()* 25; - - monster_melee(self.enemy, autocvar_g_monster_brute_chainsaw_damage, 0.3, DEATH_MONSTER_BRUTE_BLADE, TRUE); - - if(self.brute_cycles <= 4) - defer(0.2, brute_blade); -} - -void brute_uzi() -{ - self.brute_cycles += 1; - - monster_makevectors(self.enemy); - - W_SetupShot (self, autocvar_g_antilag_bullets && 18000 >= autocvar_g_antilag_bullets, 0, "weapons/uzi_fire.wav", CH_WEAPON_A, autocvar_g_monster_brute_attack_uzi_damage); - fireBallisticBullet(w_shotorg, w_shotdir, 0.02, 18000, 5, autocvar_g_monster_brute_attack_uzi_damage, autocvar_g_monster_brute_attack_uzi_force, DEATH_MONSTER_BRUTE_UZI, 0, 1, 115); - endFireBallisticBullet(); - - if(self.brute_cycles <= autocvar_g_monster_brute_attack_uzi_bullets) - defer(0.1, brute_uzi); -} - -void brute_grenade_explode() -{ - pointparticles(particleeffectnum("grenade_explode"), self.origin, '0 0 0', 1); - sound(self, CH_SHOTS, "weapons/grenade_impact.wav", VOL_BASE, ATTN_NORM); - - self.event_damage = func_null; - self.takedamage = DAMAGE_NO; - - if(self.movetype == MOVETYPE_NONE) - self.velocity = self.oldvelocity; - - RadiusDamage (self, self.realowner, autocvar_g_monster_brute_attack_grenade_damage, autocvar_g_monster_brute_attack_grenade_edgedamage, autocvar_g_monster_brute_attack_grenade_radius, world, autocvar_g_monster_brute_attack_grenade_force, self.projectiledeathtype, other); - - remove (self); -} - -void brute_grenade_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) -{ - if (self.health <= 0) - return; - - if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions - return; // g_projectiles_damage says to halt - - self.health = self.health - damage; - - if (self.health <= 0) - W_PrepareExplosionByDamage(attacker, self.use); -} - -void brute_grenade_touch() -{ - PROJECTILE_TOUCH; - - self.use (); -} - -void brute_grenade_think() -{ - self.nextthink = time; - if (time > self.cnt) - { - other = world; - brute_grenade_explode(); - return; - } -} - -void brute_grenade() -{ - entity gren; - - W_SetupShot_ProjectileSize (self, '-3 -3 -3', '3 3 3', FALSE, 4, "weapons/grenade_fire.wav", CH_WEAPON_A, autocvar_g_monster_brute_attack_grenade_damage); - w_shotdir = v_forward; // no TrueAim for grenades please - - gren = spawn (); - gren.owner = gren.realowner = self; - gren.classname = "grenade"; - gren.bot_dodge = TRUE; - gren.bot_dodgerating = autocvar_g_monster_brute_attack_grenade_damage; - gren.movetype = MOVETYPE_BOUNCE; - PROJECTILE_MAKETRIGGER(gren); - gren.projectiledeathtype = DEATH_MONSTER_BRUTE_GRENADE; - setorigin(gren, w_shotorg); - setsize(gren, '-3 -3 -3', '3 3 3'); - - gren.cnt = time + 5; - gren.nextthink = time; - gren.think = brute_grenade_think; - gren.use = brute_grenade_explode; - gren.touch = brute_grenade_touch; - - gren.takedamage = DAMAGE_YES; - gren.health = autocvar_g_balance_grenadelauncher_primary_health; - gren.damageforcescale = autocvar_g_balance_grenadelauncher_primary_damageforcescale; - gren.event_damage = brute_grenade_damage; - gren.damagedbycontents = TRUE; - gren.missile_flags = MIF_SPLASH | MIF_ARC; - W_SETUPPROJECTILEVELOCITY_UP(gren, g_balance_grenadelauncher_primary); - - gren.angles = vectoangles (gren.velocity); - gren.flags = FL_PROJECTILE; - - CSQCProjectile(gren, TRUE, PROJECTILE_GRENADE, TRUE); -} - -float brute_attack(float attack_type) -{ - switch(attack_type) - { - case MONSTER_ATTACK_MELEE: - { - self.brute_cycles = 0; - monsters_setframe(brute_anim_swing); - self.attack_finished_single = time + 1.3; - brute_blade(); - - return TRUE; - } - case MONSTER_ATTACK_RANGED: - { - self.brute_cycles = 0; - if(random() <= autocvar_g_monster_brute_attack_uzi_chance) - { - monsters_setframe(brute_anim_pain); - self.attack_finished_single = time + 0.8; - defer(0.1, brute_uzi); - } - else - { - monster_makevectors(self.enemy); - brute_grenade(); - monsters_setframe(brute_anim_pain); - self.attack_finished_single = time + 1.2; - } - - return TRUE; - } - } - - return FALSE; -} - -void brute_die() -{ - Monster_CheckDropCvars ("brute"); - - self.think = monster_dead_think; - self.nextthink = time + self.ticrate; - self.ltime = time + 5; - monsters_setframe(brute_anim_die); - - monster_hook_death(); // for post-death mods -} - -void brute_spawn() -{ - if not(self.health) - self.health = autocvar_g_monster_brute_health; - - self.damageforcescale = 0.003; - self.classname = "monster_brute"; - self.monster_attackfunc = brute_attack; - self.nextthink = time + random() * 0.5 + 0.1; - self.think = brute_think; - self.weapon = WEP_GRENADE_LAUNCHER; - - monsters_setframe(brute_anim_idle); - - monster_setupsounds("brute"); - - monster_hook_spawn(); // for post-spawn mods -} - -void spawnfunc_monster_brute() -{ - if not(autocvar_g_monster_brute) { remove(self); return; } - - self.monster_spawnfunc = spawnfunc_monster_brute; - - if(Monster_CheckAppearFlags(self)) - return; - - if not (monster_initialize( - "Brute", MONSTER_BRUTE, - BRUTE_MIN, BRUTE_MAX, - FALSE, - brute_die, brute_spawn)) - { - remove(self); - return; - } -} - -#endif // SVQC diff --git a/qcsrc/server/monsters/monster/cerberus.qc b/qcsrc/server/monsters/monster/cerberus.qc deleted file mode 100644 index 38b57e76c..000000000 --- a/qcsrc/server/monsters/monster/cerberus.qc +++ /dev/null @@ -1,118 +0,0 @@ -const vector CERBERUS_MIN = '-16 -16 -24'; -const vector CERBERUS_MAX = '16 16 12'; - -string CERBERUS_MODEL = "models/monsters/dog.dpm"; - -#ifdef SVQC -float autocvar_g_monster_cerberus; -float autocvar_g_monster_cerberus_health; -float autocvar_g_monster_cerberus_bite_damage; -float autocvar_g_monster_cerberus_attack_jump_damage; -float autocvar_g_monster_cerberus_speed_walk; -float autocvar_g_monster_cerberus_speed_run; - -const float cerberus_anim_idle = 0; -const float cerberus_anim_walk = 1; -const float cerberus_anim_run = 2; -const float cerberus_anim_attack = 3; -const float cerberus_anim_die = 4; -const float cerberus_anim_pain = 5; - -void cerberus_think() -{ - self.think = cerberus_think; - self.nextthink = time + self.ticrate; - - monster_move(autocvar_g_monster_cerberus_speed_run, autocvar_g_monster_cerberus_speed_walk, 50, cerberus_anim_run, cerberus_anim_walk, cerberus_anim_idle); -} - -void cerberus_touch_jump() -{ - if (other.takedamage) - if (vlen(self.velocity) > 300) - { - Damage(self.enemy, self, self, autocvar_g_monster_cerberus_attack_jump_damage * monster_skill, DEATH_MONSTER_CERBERUS_JUMP, self.enemy.origin, normalize(self.enemy.origin - self.origin)); - self.touch = MonsterTouch; - } - - if(trace_dphitcontents) - self.touch = MonsterTouch; -} - -float cerberus_attack(float attack_type) -{ - switch(attack_type) - { - case MONSTER_ATTACK_MELEE: - { - monsters_setframe(cerberus_anim_attack); - self.attack_finished_single = time + 0.7; - monster_melee(self.enemy, autocvar_g_monster_cerberus_bite_damage, 0.2, DEATH_MONSTER_CERBERUS_BITE, TRUE); - - return TRUE; - } - case MONSTER_ATTACK_RANGED: - { - makevectors(self.angles); - if(monster_leap(cerberus_anim_attack, cerberus_touch_jump, v_forward * 300 + '0 0 200', 0.8)) - return TRUE; - } - } - - return FALSE; -} - -void cerberus_die() -{ - Monster_CheckDropCvars ("cerberus"); - - self.think = monster_dead_think; - self.nextthink = time + self.ticrate; - self.ltime = time + 5; - monsters_setframe(cerberus_anim_die); - - monster_hook_death(); // for post-death mods -} - -void cerberus_spawn() -{ - if not(self.health) - self.health = autocvar_g_monster_cerberus_health; - - self.damageforcescale = 0; - self.classname = "monster_cerberus"; - self.monster_attackfunc = cerberus_attack; - self.nextthink = time + random() * 0.5 + 0.1; - self.think = cerberus_think; - - monsters_setframe(cerberus_anim_idle); - - monster_setupsounds("cerberus"); - - monster_hook_spawn(); // for post-spawn mods -} - -void spawnfunc_monster_cerberus() -{ - if not(autocvar_g_monster_cerberus) { remove(self); return; } - - self.monster_spawnfunc = spawnfunc_monster_cerberus; - - if(Monster_CheckAppearFlags(self)) - return; - - if not (monster_initialize( - "Cerberus", MONSTER_CERBERUS, - CERBERUS_MIN, CERBERUS_MAX, - FALSE, - cerberus_die, cerberus_spawn)) - { - remove(self); - return; - } -} - -// compatibility with old spawns -void spawnfunc_monster_dog() { spawnfunc_monster_cerberus(); } - -#endif // SVQC diff --git a/qcsrc/server/monsters/monster/knight.qc b/qcsrc/server/monsters/monster/knight.qc deleted file mode 100644 index 816ac600f..000000000 --- a/qcsrc/server/monsters/monster/knight.qc +++ /dev/null @@ -1,319 +0,0 @@ -const vector KNIGHT_MIN = '-20 -20 -32'; -const vector KNIGHT_MAX = '20 20 41'; - -string KNIGHT_MODEL = "models/monsters/hknight.mdl"; - -#ifdef SVQC -float autocvar_g_monster_knight; -float autocvar_g_monster_knight_health; -float autocvar_g_monster_knight_melee_damage; -float autocvar_g_monster_knight_inferno_damage; -float autocvar_g_monster_knight_inferno_damagetime; -float autocvar_g_monster_knight_inferno_chance; -float autocvar_g_monster_knight_speed_walk; -float autocvar_g_monster_knight_speed_run; -float autocvar_g_monster_knight_fireball_damage; -float autocvar_g_monster_knight_fireball_force; -float autocvar_g_monster_knight_fireball_radius; -float autocvar_g_monster_knight_fireball_chance; -float autocvar_g_monster_knight_fireball_edgedamage; -float autocvar_g_monster_knight_spike_chance; -float autocvar_g_monster_knight_spike_force; -float autocvar_g_monster_knight_spike_radius; -float autocvar_g_monster_knight_spike_edgedamage; -float autocvar_g_monster_knight_spike_damage; -float autocvar_g_monster_knight_jump_chance; -float autocvar_g_monster_knight_jump_damage; -float autocvar_g_monster_knight_jump_dist; - -const float knight_anim_stand = 0; -const float knight_anim_walk = 1; -const float knight_anim_run = 2; -const float knight_anim_pain = 3; -const float knight_anim_death1 = 4; -const float knight_anim_death2 = 5; -const float knight_anim_charge1 = 6; -const float knight_anim_magic1 = 7; -const float knight_anim_magic2 = 8; -const float knight_anim_charge2 = 9; -const float knight_anim_slice = 10; -const float knight_anim_smash = 11; -const float knight_anim_wattack = 12; -const float knight_anim_magic3 = 13; - -.float knight_cycles; - -void knight_think() -{ - self.think = knight_think; - self.nextthink = time + self.ticrate; - - monster_move(autocvar_g_monster_knight_speed_run, autocvar_g_monster_knight_speed_walk, 100, knight_anim_run, knight_anim_walk, knight_anim_stand); -} - -void knight_inferno() -{ - if not(self.enemy) - return; - - traceline((self.absmin + self.absmax) * 0.5, (self.enemy.absmin + self.enemy.absmax) * 0.5, TRUE, world); - if (trace_fraction != 1) - return; // not visible - - self.enemy.effects |= EF_MUZZLEFLASH; - sound(self.enemy, CHAN_AUTO, "player/lava.wav", 1, ATTN_NORM); - - if(vlen(self.enemy.origin - self.origin) <= 2000) - Fire_AddDamage(self.enemy, self, autocvar_g_monster_knight_inferno_damage * monster_skill, autocvar_g_monster_knight_inferno_damagetime, DEATH_MONSTER_KNIGHT_INFERNO); -} - -void knight_fireball_explode() -{ - entity e; - if(self) - { - pointparticles(particleeffectnum("fireball_explode"), self.origin, '0 0 0', 1); - - RadiusDamage(self, self.realowner, autocvar_g_monster_knight_fireball_damage, autocvar_g_monster_knight_fireball_edgedamage, autocvar_g_monster_knight_fireball_force, world, autocvar_g_monster_knight_fireball_radius, self.projectiledeathtype, world); - - for(e = world; (e = findfloat(e, takedamage, DAMAGE_AIM)); ) if(vlen(e.origin - self.origin) <= autocvar_g_monster_knight_fireball_radius) - Fire_AddDamage(e, self, 5 * monster_skill, autocvar_g_monster_knight_inferno_damagetime, self.projectiledeathtype); - - remove(self); - } -} - -void knight_fireball_touch() -{ - PROJECTILE_TOUCH; - - knight_fireball_explode(); -} - -void knight_fireball() -{ - entity missile = spawn(); - vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin); - - monster_makevectors(self.enemy); - - self.effects |= EF_MUZZLEFLASH; - sound(self, CHAN_WEAPON, "weapons/fireball2.wav", 1, ATTN_NORM); - - missile.owner = missile.realowner = self; - missile.solid = SOLID_TRIGGER; - missile.movetype = MOVETYPE_FLYMISSILE; - missile.projectiledeathtype = DEATH_MONSTER_KNIGHT_FBALL; - setsize(missile, '-6 -6 -6', '6 6 6'); - setorigin(missile, self.origin + self.view_ofs + v_forward * 14); - missile.flags = FL_PROJECTILE; - missile.velocity = dir * 400; - missile.avelocity = '300 300 300'; - missile.nextthink = time + 5; - missile.think = knight_fireball_explode; - missile.enemy = self.enemy; - missile.touch = knight_fireball_touch; - CSQCProjectile(missile, TRUE, PROJECTILE_FIREMINE, TRUE); -} - -void knight_spike_explode() -{ - if(self) - { - pointparticles(particleeffectnum("TE_WIZSPIKE"), self.origin, '0 0 0', 1); - - RadiusDamage (self, self.realowner, autocvar_g_monster_knight_spike_damage, autocvar_g_monster_knight_spike_edgedamage, autocvar_g_monster_knight_spike_force, world, autocvar_g_monster_knight_spike_radius, DEATH_MONSTER_KNIGHT_SPIKE, other); - remove(self); - } -} - -void knight_spike_touch() -{ - PROJECTILE_TOUCH; - - knight_spike_explode(); -} - -void knight_spike() -{ - entity missile; - vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin); - - self.effects |= EF_MUZZLEFLASH; - - missile = spawn (); - missile.owner = missile.realowner = self; - missile.solid = SOLID_TRIGGER; - missile.movetype = MOVETYPE_FLYMISSILE; - setsize (missile, '0 0 0', '0 0 0'); - setorigin(missile, self.origin + '0 0 10' + v_forward * 14); - missile.scale = self.scale; - missile.flags = FL_PROJECTILE; - missile.velocity = dir * 400; - missile.avelocity = '300 300 300'; - missile.nextthink = time + 5; - missile.think = knight_spike_explode; - missile.enemy = self.enemy; - missile.touch = knight_spike_touch; - CSQCProjectile(missile, TRUE, PROJECTILE_CRYLINK, TRUE); -} - -void knight_spikes() -{ - self.knight_cycles += 1; - knight_spike(); - - if(self.knight_cycles <= 7) - defer(0.1, knight_spikes); -} - -float knight_attack_ranged() -{ - if not(self.flags & FL_ONGROUND) - return FALSE; - - self.knight_cycles = 0; - - RandomSelection_Init(); - RandomSelection_Add(world, 1, "", autocvar_g_monster_knight_fireball_chance, 1); - RandomSelection_Add(world, 2, "", autocvar_g_monster_knight_inferno_chance, 1); - RandomSelection_Add(world, 3, "", autocvar_g_monster_knight_spike_chance, 1); - if(self.health >= 100) RandomSelection_Add(world, 4, "", ((vlen(self.enemy.origin - self.origin) > autocvar_g_monster_knight_jump_dist) ? 1 : autocvar_g_monster_knight_jump_chance), 1); - - switch(RandomSelection_chosen_float) - { - case 1: - { - monsters_setframe(knight_anim_magic2); - self.attack_finished_single = time + 2; - defer(0.4, knight_fireball); - - return TRUE; - } - case 2: - { - self.attack_finished_single = time + 3; - defer(0.5, knight_inferno); - return TRUE; - } - case 3: - { - monsters_setframe(knight_anim_magic3); - self.attack_finished_single = time + 3; - defer(0.4, knight_spikes); - - return TRUE; - } - case 4: - { - float er = vlen(self.enemy.origin - self.origin); - - if(er >= 400 && er < 1200) - if(findtrajectorywithleading(self.origin, self.mins, self.maxs, self.enemy, 1000, 0, 10, 0, self)) - { - self.velocity = findtrajectory_velocity; - Damage(self.enemy, self, self, autocvar_g_monster_knight_jump_damage * monster_skill, DEATH_MONSTER_KNIGHT_CRUSH, self.enemy.origin, normalize(self.enemy.origin - self.origin)); - self.attack_finished_single = time + 2; - return TRUE; - } - return FALSE; - } - } - - return FALSE; -} - -float knight_attack(float attack_type) -{ - switch(attack_type) - { - case MONSTER_ATTACK_MELEE: - { - float anim; - if(random() < 0.3) - anim = knight_anim_slice; - else if(random() < 0.6) - anim = knight_anim_smash; - else - anim = knight_anim_wattack; - - monsters_setframe(anim); - self.attack_finished_single = time + 0.7; - monster_melee(self.enemy, autocvar_g_monster_knight_melee_damage, 0.3, DEATH_MONSTER_KNIGHT_MELEE, TRUE); - - return TRUE; - } - case MONSTER_ATTACK_RANGED: - { - if(knight_attack_ranged()) - return TRUE; - } - } - - return FALSE; -} - -void knight_die() -{ - float chance = random(); - Monster_CheckDropCvars ("knight"); - - self.think = monster_dead_think; - self.nextthink = time + self.ticrate; - self.ltime = time + 5; - monsters_setframe((random() > 0.5) ? knight_anim_death1 : knight_anim_death2); - - if(chance < 0.10 || self.flags & MONSTERFLAG_MINIBOSS) - if(self.candrop) - { - self.superweapons_finished = time + autocvar_g_balance_superweapons_time + 5; // give the player a few seconds to find the weapon - self.weapon = WEP_FIREBALL; - } - - monster_hook_death(); // for post-death mods -} - -void knight_spawn() -{ - if not(self.health) - self.health = autocvar_g_monster_knight_health; - - self.damageforcescale = 0.003; - self.classname = "monster_knight"; - self.monster_attackfunc = knight_attack; - self.nextthink = time + random() * 0.5 + 0.1; - self.think = knight_think; - - monsters_setframe(knight_anim_stand); - - monster_setupsounds("knight"); - - monster_hook_spawn(); // for post-spawn mods -} - -void spawnfunc_monster_knight() -{ - if not(autocvar_g_monster_knight) { remove(self); return; } - - self.monster_spawnfunc = spawnfunc_monster_knight; - - if(Monster_CheckAppearFlags(self)) - return; - - self.scale = 1.3; - - if not (monster_initialize( - "Knight", MONSTER_KNIGHT, - KNIGHT_MIN, KNIGHT_MAX, - FALSE, - knight_die, knight_spawn)) - { - remove(self); - return; - } -} - -// compatibility with old spawns -void spawnfunc_monster_hell_knight() { spawnfunc_monster_knight(); } - -#endif // SVQC diff --git a/qcsrc/server/monsters/monster/mage.qc b/qcsrc/server/monsters/monster/mage.qc deleted file mode 100644 index d30c20833..000000000 --- a/qcsrc/server/monsters/monster/mage.qc +++ /dev/null @@ -1,431 +0,0 @@ -const vector MAGE_MIN = '-36 -36 -24'; -const vector MAGE_MAX = '36 36 50'; - -string MAGE_MODEL = "models/monsters/mage.dpm"; - -#ifdef SVQC -float autocvar_g_monster_mage; -float autocvar_g_monster_mage_health; -float autocvar_g_monster_mage_speed; -float autocvar_g_monster_mage_attack_spike_damage; -float autocvar_g_monster_mage_attack_spike_radius; -float autocvar_g_monster_mage_attack_spike_delay; -float autocvar_g_monster_mage_attack_melee_damage; -float autocvar_g_monster_mage_attack_melee_delay; -float autocvar_g_monster_mage_heal_self; -float autocvar_g_monster_mage_heal_friends; -float autocvar_g_monster_mage_heal_minhealth; -float autocvar_g_monster_mage_heal_range; -float autocvar_g_monster_mage_heal_delay; -float autocvar_g_monster_mage_shield_time; -float autocvar_g_monster_mage_shield_delay; -float autocvar_g_monster_mage_shield_blockpercent; -float autocvar_g_monster_mage_attack_grenade_damage; -float autocvar_g_monster_mage_attack_grenade_edgedamage; -float autocvar_g_monster_mage_attack_grenade_radius; -float autocvar_g_monster_mage_attack_grenade_lifetime; -float autocvar_g_monster_mage_attack_grenade_force; -float autocvar_g_monster_mage_attack_grenade_chance; - -const float mage_anim_idle = 0; -const float mage_anim_walk = 1; -const float mage_anim_attack = 2; -const float mage_anim_pain = 3; -const float mage_anim_death = 4; -const float mage_anim_run = 5; - -void() mage_heal; -void() mage_shield; -void() mage_shield_die; - -float friend_needshelp(entity e) -{ - if(e == world) - return FALSE; - if(e.health <= 0) - return FALSE; - if(vlen(e.origin - self.origin) > autocvar_g_monster_mage_heal_range) - return FALSE; - if(IsDifferentTeam(e, self)) - return FALSE; - if(e.frozen) - return FALSE; - if(!IS_PLAYER(e)) - return (e.health < e.max_health); - if(e.items & IT_INVINCIBLE) - return FALSE; - - switch(self.skin) - { - case 0: - { - if(e.health < autocvar_g_balance_health_regenstable) - return TRUE; - break; - } - case 1: - { - if((e.ammo_cells && e.ammo_cells < g_pickup_cells_max) || (e.ammo_rockets && e.ammo_rockets < g_pickup_rockets_max) || (e.ammo_nails && e.ammo_nails < g_pickup_nails_max) || (e.ammo_shells && e.ammo_shells < g_pickup_shells_max)) - return TRUE; - break; - } - case 2: - { - if(e.armorvalue < autocvar_g_balance_armor_regenstable) - return TRUE; - break; - } - case 3: - { - if(e.health > 0) - return TRUE; - break; - } - } - - return FALSE; -} - -void mage_think() -{ - entity head; - float need_help = FALSE; - - FOR_EACH_PLAYER(head) - if(friend_needshelp(head)) - { - need_help = TRUE; - break; // found 1 player near us who is low on health - } - if(!need_help) - FOR_EACH_MONSTER(head) - if(head != self) - if(friend_needshelp(head)) - { - need_help = TRUE; - break; // found 1 player near us who is low on health - } - - self.think = mage_think; - self.nextthink = time + self.ticrate; - - if(self.weaponentity) - if(time >= self.weaponentity.ltime) - mage_shield_die(); - - if(self.health < autocvar_g_monster_mage_heal_minhealth || need_help) - if(time >= self.attack_finished_single) - if(random() < 0.5) - mage_heal(); - - if(self.enemy) - if(self.health < self.max_health) - if(time >= self.lastshielded) - if(random() < 0.5) - mage_shield(); - - monster_move(autocvar_g_monster_mage_speed, autocvar_g_monster_mage_speed, 50, mage_anim_walk, mage_anim_run, mage_anim_idle); -} - -void mageattack_melee() -{ - monster_melee(self.enemy, autocvar_g_monster_mage_attack_melee_damage, 0.3, DEATH_MONSTER_MAGE, TRUE); -} - -void mage_grenade_explode() -{ - pointparticles(particleeffectnum("explosion_small"), self.origin, '0 0 0', 1); - - sound(self, CH_SHOTS, "weapons/grenade_impact.wav", VOL_BASE, ATTN_NORM); - RadiusDamage (self, self.realowner, autocvar_g_monster_mage_attack_grenade_damage, autocvar_g_monster_mage_attack_grenade_edgedamage, autocvar_g_monster_mage_attack_grenade_radius, world, autocvar_g_monster_mage_attack_grenade_force, DEATH_MONSTER_MAGE, other); - remove(self); -} - -void mage_grenade_touch() -{ - if(IS_PLAYER(other)) - { - PROJECTILE_TOUCH; - mage_grenade_explode(); - return; - } -} - -void mage_throw_itemgrenade() -{ - makevectors(self.angles); - - W_SetupShot_ProjectileSize (self, '-64 -64 -64', '64 64 64', FALSE, 4, "", CH_WEAPON_A, autocvar_g_monster_mage_attack_grenade_damage); - w_shotdir = v_forward; // no TrueAim for grenades please - - entity gren = spawn (); - gren.owner = gren.realowner = self; - gren.classname = "grenade"; - gren.bot_dodge = FALSE; - gren.movetype = MOVETYPE_BOUNCE; - gren.solid = SOLID_TRIGGER; - gren.projectiledeathtype = DEATH_MONSTER_MAGE; - setorigin(gren, w_shotorg); - setsize(gren, '-64 -64 -64', '64 64 64'); - - gren.nextthink = time + autocvar_g_monster_mage_attack_grenade_lifetime; - gren.think = mage_grenade_explode; - gren.use = mage_grenade_explode; - gren.touch = mage_grenade_touch; - - gren.missile_flags = MIF_SPLASH | MIF_ARC; - W_SETUPPROJECTILEVELOCITY_UP(gren, g_monster_mage_attack_grenade); - - gren.flags = FL_PROJECTILE; - - setmodel(gren, "models/items/g_h50.md3"); - - self.attack_finished_single = time + 1.5; -} - -void mage_spike_explode() -{ - self.event_damage = func_null; - - pointparticles(particleeffectnum("explosion_small"), self.origin, '0 0 0', 1); - RadiusDamage (self, self.realowner, autocvar_g_monster_mage_attack_spike_damage, autocvar_g_monster_mage_attack_spike_damage * 0.5, autocvar_g_monster_mage_attack_spike_radius, world, 0, DEATH_MONSTER_MAGE, other); - - remove (self); -} - -void mage_spike_touch() -{ - PROJECTILE_TOUCH; - - mage_spike_explode(); -} - -void mage_spike_think() -{ - if(self.enemy.health <= 0 || self.owner.health <= 0 || time >= self.ltime) - { - mage_spike_explode(); - return; - } - - vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin); - - UpdateCSQCProjectile(self); - - if (monster_skill == 3) - self.velocity = dir * 350; - else - self.velocity = dir * 250; - - self.nextthink = time + 0.2; - self.think = mage_spike_think; -} - -void mage_spike() -{ - entity missile; - vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin); - - makevectors(self.angles); - - missile = spawn (); - missile.owner = missile.realowner = self; - missile.think = mage_spike_think; - missile.ltime = time + 7; - missile.nextthink = time; - missile.solid = SOLID_BBOX; - missile.movetype = MOVETYPE_FLYMISSILE; - missile.flags = FL_PROJECTILE; - setorigin(missile, self.origin + v_forward * 14 + '0 0 30' + v_right * -14); - setsize (missile, '0 0 0', '0 0 0'); - missile.velocity = dir * 400; - missile.avelocity = '300 300 300'; - missile.enemy = self.enemy; - missile.touch = mage_spike_touch; - - CSQCProjectile(missile, TRUE, PROJECTILE_MAGE_SPIKE, TRUE); -} - -void mage_heal() -{ - entity head; - float washealed = FALSE; - - for(head = world; (head = findfloat(head, monster_attack, TRUE)); ) if(friend_needshelp(head)) - { - washealed = TRUE; - string fx = ""; - if(IS_PLAYER(head)) - { - switch(self.skin) - { - case 0: - if(head.health < autocvar_g_balance_health_regenstable) head.health = bound(0, head.health + autocvar_g_monster_mage_heal_friends, autocvar_g_balance_health_regenstable); - fx = "healing_fx"; - break; - case 1: - if(head.ammo_cells) head.ammo_cells = bound(head.ammo_cells, head.ammo_cells + 1, g_pickup_cells_max); - if(head.ammo_rockets) head.ammo_rockets = bound(head.ammo_rockets, head.ammo_rockets + 1, g_pickup_rockets_max); - if(head.ammo_shells) head.ammo_shells = bound(head.ammo_shells, head.ammo_shells + 2, g_pickup_shells_max); - if(head.ammo_nails) head.ammo_nails = bound(head.ammo_nails, head.ammo_nails + 5, g_pickup_nails_max); - fx = "ammoregen_fx"; - break; - case 2: - if(head.armorvalue < autocvar_g_balance_armor_regenstable) - { - head.armorvalue = bound(0, head.armorvalue + autocvar_g_monster_mage_heal_friends, autocvar_g_balance_armor_regenstable); - fx = "armorrepair_fx"; - } - break; - case 3: - head.health = bound(0, head.health - ((head == self) ? autocvar_g_monster_mage_heal_self : autocvar_g_monster_mage_heal_friends), autocvar_g_balance_health_regenstable); - fx = "rage"; - break; - } - - pointparticles(particleeffectnum(fx), head.origin, '0 0 0', 1); - } - else - { - pointparticles(particleeffectnum("healing_fx"), head.origin, '0 0 0', 1); - head.health = bound(0, head.health + autocvar_g_monster_mage_heal_friends, head.max_health); - WaypointSprite_UpdateHealth(head.sprite, head.health); - } - } - - if(washealed) - { - monsters_setframe(mage_anim_attack); - self.attack_finished_single = time + autocvar_g_monster_mage_heal_delay; - } -} - -void mage_shield_die() -{ - if not(self.weaponentity) - return; // why would this be called without a shield? - - self.armorvalue = 1; - - remove(self.weaponentity); - - self.weaponentity = world; -} - -void mage_shield() -{ - if(self.weaponentity) - return; // already have a shield - - entity shield = spawn(); - - shield.owner = self; - shield.team = self.team; - shield.ltime = time + autocvar_g_monster_mage_shield_time; - shield.health = 70; - shield.classname = "shield"; - shield.effects = EF_ADDITIVE; - shield.movetype = MOVETYPE_NOCLIP; - shield.solid = SOLID_TRIGGER; - shield.avelocity = '7 0 11'; - shield.scale = self.scale * 0.6; - - setattachment(shield, self, ""); - setmodel(shield, "models/ctf/shield.md3"); - setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs); - - self.weaponentity = shield; - - self.lastshielded = time + autocvar_g_monster_mage_shield_delay; - - monsters_setframe(mage_anim_attack); - self.attack_finished_single = time + 1; - - self.armorvalue = autocvar_g_monster_mage_shield_blockpercent / 100; -} - -float mage_attack(float attack_type) -{ - switch(attack_type) - { - case MONSTER_ATTACK_MELEE: - { - monsters_setframe(mage_anim_attack); - self.attack_finished_single = time + autocvar_g_monster_mage_attack_melee_delay; - defer(0.2, mageattack_melee); - - return TRUE; - } - case MONSTER_ATTACK_RANGED: - { - if(random() < autocvar_g_monster_mage_attack_grenade_chance / 100) - { - mage_throw_itemgrenade(); - return TRUE; - } - - monsters_setframe(mage_anim_attack); - self.attack_finished_single = time + autocvar_g_monster_mage_attack_spike_delay; - defer(0.2, mage_spike); - - return TRUE; - } - } - - return FALSE; -} - -void mage_die() -{ - Monster_CheckDropCvars ("mage"); - - self.think = monster_dead_think; - self.nextthink = time + self.ticrate; - self.ltime = time + 5; - monsters_setframe(mage_anim_death); - - monster_hook_death(); // for post-death mods -} - -void mage_spawn() -{ - if not(self.health) - self.health = autocvar_g_monster_mage_health; - - self.damageforcescale = 0.003; - self.classname = "monster_mage"; - self.monster_attackfunc = mage_attack; - self.nextthink = time + random() * 0.5 + 0.1; - self.think = mage_think; - - monsters_setframe(mage_anim_walk); - - monster_setupsounds("mage"); - - monster_hook_spawn(); // for post-spawn mods -} - -void spawnfunc_monster_mage() -{ - if not(autocvar_g_monster_mage) { remove(self); return; } - - self.monster_spawnfunc = spawnfunc_monster_mage; - - if(Monster_CheckAppearFlags(self)) - return; - - if not (monster_initialize( - "Mage", MONSTER_MAGE, - MAGE_MIN, MAGE_MAX, - FALSE, - mage_die, mage_spawn)) - { - remove(self); - return; - } -} - -// compatibility with old spawns -void spawnfunc_monster_shalrath() { spawnfunc_monster_mage(); } - -#endif // SVQC diff --git a/qcsrc/server/monsters/monster/shambler.qc b/qcsrc/server/monsters/monster/shambler.qc deleted file mode 100644 index ec782216c..000000000 --- a/qcsrc/server/monsters/monster/shambler.qc +++ /dev/null @@ -1,159 +0,0 @@ -const vector SHAMBLER_MIN = '-41 -41 -31'; -const vector SHAMBLER_MAX = '41 41 65'; - -string SHAMBLER_MODEL = "models/monsters/shambler.mdl"; - -#ifdef SVQC -float autocvar_g_monster_shambler; -float autocvar_g_monster_shambler_health; -float autocvar_g_monster_shambler_damage; -float autocvar_g_monster_shambler_attack_lightning_damage; -float autocvar_g_monster_shambler_attack_claw_damage; -float autocvar_g_monster_shambler_speed_walk; -float autocvar_g_monster_shambler_speed_run; - -const float shambler_anim_stand = 0; -const float shambler_anim_walk = 1; -const float shambler_anim_run = 2; -const float shambler_anim_smash = 3; -const float shambler_anim_swingr = 4; -const float shambler_anim_swingl = 5; -const float shambler_anim_magic = 6; -const float shambler_anim_pain = 7; -const float shambler_anim_death = 8; - -void shambler_think() -{ - self.think = shambler_think; - self.nextthink = time + self.ticrate; - - monster_move(autocvar_g_monster_shambler_speed_run, autocvar_g_monster_shambler_speed_walk, 300, shambler_anim_run, shambler_anim_walk, shambler_anim_stand); -} - -void shambler_smash() -{ - monster_melee(self.enemy, autocvar_g_monster_shambler_damage, 0.3, DEATH_MONSTER_SHAMBLER_SMASH, TRUE); -} - -void shambler_delayedsmash() -{ - monsters_setframe(shambler_anim_smash); - defer(0.7, shambler_smash); - self.attack_finished_single = time + 1.1; -} - -void shambler_swing() -{ - float r = (random() < 0.5); - monsters_setframe((r) ? shambler_anim_swingr : shambler_anim_swingl); - monster_melee(self.enemy, autocvar_g_monster_shambler_attack_claw_damage, 0.3, DEATH_MONSTER_SHAMBLER_CLAW, TRUE); - self.attack_finished_single = time + 0.8; - if(r) - defer(0.5, shambler_swing); -} - -void CastLightning() -{ - local vector org, dir; - //vector v = '0 0 0'; - - self.effects |= EF_MUZZLEFLASH; - - org = self.origin + '0 0 40'; - - dir = self.enemy.origin + '0 0 16' - org; - dir = normalize (dir); - - traceline (org, self.origin + dir * 1000, TRUE, self); - - FireRailgunBullet (org, org + dir * 1000, autocvar_g_monster_shambler_attack_lightning_damage * monster_skill, 0, 0, 0, 0, 0, DEATH_MONSTER_SHAMBLER_ZAP); - - // teamcolor / hit beam effect - //v = WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos); - //WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3"), org, v); - - te_csqc_lightningarc(org, trace_endpos); -} - -float shambler_attack(float attack_type) -{ - switch(attack_type) - { - case MONSTER_ATTACK_MELEE: - { - float chance = random(); - - if(chance > 0.6) - shambler_delayedsmash(); - else - shambler_swing(); - - return TRUE; - } - case MONSTER_ATTACK_RANGED: - { - monsters_setframe(shambler_anim_magic); - self.attack_finished_single = time + 1.1; - defer(0.6, CastLightning); - - return TRUE; - } - } - - return FALSE; -} - -void shambler_die() -{ - Monster_CheckDropCvars ("shambler"); - - self.think = monster_dead_think; - self.nextthink = time + self.ticrate; - self.ltime = time + 5; - monsters_setframe(shambler_anim_death); - - monster_hook_death(); // for post-death mods -} - -void shambler_spawn() -{ - if not(self.health) - self.health = autocvar_g_monster_shambler_health; - - self.damageforcescale = 0.003; - self.classname = "monster_shambler"; - self.monster_attackfunc = shambler_attack; - self.nextthink = time + random() * 0.5 + 0.1; - self.think = shambler_think; - self.weapon = WEP_NEX; - - monsters_setframe(shambler_anim_stand); - - monster_setupsounds("shambler"); - - monster_hook_spawn(); // for post-spawn mods -} - -void spawnfunc_monster_shambler() -{ - if not(autocvar_g_monster_shambler) { remove(self); return; } - - self.monster_spawnfunc = spawnfunc_monster_shambler; - - if(Monster_CheckAppearFlags(self)) - return; - - self.scale = 1.3; - - if not (monster_initialize( - "Shambler", MONSTER_SHAMBLER, - SHAMBLER_MIN, SHAMBLER_MAX, - FALSE, - shambler_die, shambler_spawn)) - { - remove(self); - return; - } -} - -#endif // SVQC diff --git a/qcsrc/server/monsters/monster/slime.qc b/qcsrc/server/monsters/monster/slime.qc deleted file mode 100644 index 8f73ceaea..000000000 --- a/qcsrc/server/monsters/monster/slime.qc +++ /dev/null @@ -1,141 +0,0 @@ -const vector SLIME_MIN = '-16 -16 -24'; -const vector SLIME_MAX = '16 16 16'; - -string SLIME_MODEL = "models/monsters/slime.dpm"; - -#ifdef SVQC -float autocvar_g_monster_slime; -float autocvar_g_monster_slime_health; -float autocvar_g_monster_slime_speed_walk; -float autocvar_g_monster_slime_speed_run; - -const float slime_anim_walk = 0; -const float slime_anim_idle = 1; -const float slime_anim_jump = 2; -const float slime_anim_fly = 3; -const float slime_anim_die = 4; -const float slime_anim_pain = 5; - -void slime_think() -{ - self.think = slime_think; - self.nextthink = time + self.ticrate; - - monster_move(autocvar_g_monster_slime_speed_run, autocvar_g_monster_slime_speed_walk, 20, slime_anim_walk, slime_anim_walk, slime_anim_idle); -} - -void slime_touch_jump() -{ - if(self.health > 0) - if(other.health > 0) - if(other.takedamage) - if(vlen(self.velocity) > 200) - { - Damage (self, world, world, self.health + self.max_health + 200, DEATH_MONSTER_SLIME, self.origin, '0 0 0'); - - return; - } - - if(trace_dphitcontents) - { - self.touch = MonsterTouch; - self.movetype = MOVETYPE_WALK; - } -} - -float slime_attack(float attack_type) -{ - switch(attack_type) - { - case MONSTER_ATTACK_MELEE: - case MONSTER_ATTACK_RANGED: - { - makevectors(self.angles); - if(monster_leap(slime_anim_jump, slime_touch_jump, v_forward * 600 + '0 0 200', 0.5)) - return TRUE; - } - } - - return FALSE; -} - -void slime_explode() -{ - RadiusDamage(self, self, 250 * monster_skill, 15, 250 * (monster_skill * 0.7), world, 250, DEATH_MONSTER_SLIME, world); - pointparticles(particleeffectnum("explosion_medium"), self.origin, '0 0 0', 1); - sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTN_NORM); - - setmodel(self, ""); -} - -void slime_dead() -{ - self.health = -100; // gibbed - slime_explode(); - - Monster_CheckDropCvars ("slime"); // drop items after exploding to prevent player picking up item before dying - - self.deadflag = DEAD_DEAD; - self.think = Monster_Fade; - self.nextthink = time + 0.1; - - monster_hook_death(); - - self.event_damage = func_null; // reset by monster_hook_death - self.takedamage = DAMAGE_NO; -} - -void slime_die() -{ - self.think = slime_dead; - self.nextthink = time; - self.event_damage = func_null; - self.movetype = MOVETYPE_NONE; - self.enemy = world; - self.health = 0; - - self.SendFlags |= MSF_MOVE | MSF_STATUS; -} - -void slime_spawn() -{ - if not(self.health) - self.health = autocvar_g_monster_slime_health; - - self.damageforcescale = 0.003; - self.classname = "monster_slime"; - self.monster_attackfunc = slime_attack; - self.nextthink = time + random() * 0.5 + 0.1; - self.think = slime_think; - - monsters_setframe(slime_anim_idle); - - monster_setupsounds("slime"); - - monster_hook_spawn(); // for post-spawn mods -} - -void spawnfunc_monster_slime() -{ - if not(autocvar_g_monster_slime) { remove(self); return; } - - self.monster_spawnfunc = spawnfunc_monster_slime; - - if(Monster_CheckAppearFlags(self)) - return; - - if not (monster_initialize( - "Slime", MONSTER_SLIME, - SLIME_MIN, SLIME_MAX, - FALSE, - slime_die, slime_spawn)) - { - remove(self); - return; - } -} - -// compatibility with old spawns -void spawnfunc_monster_tarbaby() { spawnfunc_monster_slime(); } - -#endif // SVQC diff --git a/qcsrc/server/monsters/monster/spider.qc b/qcsrc/server/monsters/monster/spider.qc deleted file mode 100644 index b72a857e7..000000000 --- a/qcsrc/server/monsters/monster/spider.qc +++ /dev/null @@ -1,217 +0,0 @@ -const vector SPIDER_MIN = '-18 -18 -25'; -const vector SPIDER_MAX = '18 18 30'; - -string SPIDER_MODEL = "models/monsters/spider.dpm"; - -#ifdef SVQC -float autocvar_g_monster_spider; -float autocvar_g_monster_spider_stopspeed; -float autocvar_g_monster_spider_attack_leap_delay; -float autocvar_g_monster_spider_attack_stand_damage; -float autocvar_g_monster_spider_attack_stand_delay; -float autocvar_g_monster_spider_attack_fire_time; -float autocvar_g_monster_spider_health; -float autocvar_g_monster_spider_speed_walk; -float autocvar_g_monster_spider_speed_run; -float autocvar_g_monster_spider_attack_type; - -const float spider_anim_idle = 0; -const float spider_anim_walk = 1; -const float spider_anim_attack = 2; -const float spider_anim_attack2 = 3; - -.float spider_type; // used to switch between fire & ice attacks -const float SPIDER_TYPE_ICE = 0; -const float SPIDER_TYPE_FIRE = 1; - -void spider_think() -{ - self.think = spider_think; - self.nextthink = time + self.ticrate; - - monster_move(autocvar_g_monster_spider_speed_run, autocvar_g_monster_spider_speed_walk, autocvar_g_monster_spider_stopspeed, spider_anim_walk, spider_anim_walk, spider_anim_idle); -} - -void spider_web_explode() -{ - entity e; - if(self) - { - float damg = 0, edamg = 0, rad = 1; - switch(self.realowner.spider_type) - { - case SPIDER_TYPE_ICE: - rad = 25; - pointparticles(particleeffectnum("electro_impact"), self.origin, '0 0 0', 1); - break; - case SPIDER_TYPE_FIRE: - pointparticles(particleeffectnum("fireball_explode"), self.origin, '0 0 0', 1); - damg = 15; - rad = 25; - edamg = 6; - break; - } - - RadiusDamage(self, self.realowner, damg, edamg, 0, world, rad, DEATH_MONSTER_SPIDER_FIRE, world); // ice deals no damage anyway - - for(e = findradius(self.origin, rad); e; e = e.chain) if(e.takedamage && e.deadflag == DEAD_NO) if(e.health > 0) - { - switch(self.realowner.spider_type) - { - case SPIDER_TYPE_ICE: - Freeze(e, 0.3, 2, FALSE); - break; - case SPIDER_TYPE_FIRE: - Fire_AddDamage(e, self.realowner, 5 * monster_skill, autocvar_g_monster_spider_attack_fire_time, DEATH_MONSTER_SPIDER_FIRE); - break; - } - } - - remove(self); - } -} - -void spider_web_touch() -{ - PROJECTILE_TOUCH; - - spider_web_explode(); -} - -void spider_shootweb(float ptype) -{ - float p = 0; - string snd = ""; - switch(ptype) - { - case SPIDER_TYPE_ICE: - p = PROJECTILE_ELECTRO; - snd = "weapons/electro_fire2.wav"; - break; - case SPIDER_TYPE_FIRE: - p = PROJECTILE_FIREMINE; - snd = "weapons/fireball_fire.wav"; - break; - } - - vector fmins = '-4 -4 -4', fmaxs = '4 4 4'; - - W_SetupShot_ProjectileSize(self, fmins, fmaxs, FALSE, 2, snd, CH_WEAPON_A, 0); - - w_shotdir = v_forward; // no TrueAim for grenades please - - entity proj = spawn (); - proj.classname = "plasma"; - proj.owner = proj.realowner = self; - proj.use = spider_web_touch; - proj.think = adaptor_think2use_hittype_splash; - proj.bot_dodge = TRUE; - proj.bot_dodgerating = 0; - proj.nextthink = time + 5; - PROJECTILE_MAKETRIGGER(proj); - proj.projectiledeathtype = DEATH_MONSTER_SPIDER_FIRE; - setorigin(proj, w_shotorg); - - //proj.glow_size = 50; - //proj.glow_color = 45; - proj.movetype = MOVETYPE_BOUNCE; - W_SETUPPROJECTILEVELOCITY_UP(proj, g_monster_spider_attack_web); - proj.touch = spider_web_touch; - setsize(proj, fmins, fmaxs); - proj.takedamage = DAMAGE_NO; - proj.damageforcescale = 0; - proj.health = 500; - proj.event_damage = func_null; - proj.flags = FL_PROJECTILE; - proj.damagedbycontents = TRUE; - - proj.bouncefactor = 0.3; - proj.bouncestop = 0.05; - proj.missile_flags = MIF_SPLASH | MIF_ARC; - - CSQCProjectile(proj, TRUE, p, TRUE); -} - -float spider_attack(float attack_type) -{ - switch(attack_type) - { - case MONSTER_ATTACK_MELEE: - { - monster_melee(self.enemy, autocvar_g_monster_spider_attack_stand_damage, 0.3, DEATH_MONSTER_SPIDER, TRUE); - monsters_setframe((random() > 0.5) ? spider_anim_attack : spider_anim_attack2); - self.attack_finished_single = time + autocvar_g_monster_spider_attack_stand_delay; - - return TRUE; - } - case MONSTER_ATTACK_RANGED: - { - if(self.enemy.frozen) - return FALSE; - - monsters_setframe(spider_anim_attack2); - self.attack_finished_single = time + autocvar_g_monster_spider_attack_leap_delay; - monster_makevectors(self.enemy); - spider_shootweb(self.spider_type); - - return TRUE; - } - } - - return FALSE; -} - -void spider_die() -{ - Monster_CheckDropCvars ("spider"); - - self.think = monster_dead_think; - self.nextthink = time + self.ticrate; - self.ltime = time + 5; - monsters_setframe(spider_anim_attack); - self.angles += '180 0 0'; - - monster_hook_death(); // for post-death mods -} - -void spider_spawn() -{ - if not(self.health) - self.health = autocvar_g_monster_spider_health; - - self.classname = "monster_spider"; - self.nextthink = time + random() * 0.5 + 0.1; - self.monster_attackfunc = spider_attack; - self.think = spider_think; - - monsters_setframe(spider_anim_idle); - - monster_setupsounds("spider"); - - if not(self.spider_type) - self.spider_type = autocvar_g_monster_spider_attack_type; - - monster_hook_spawn(); // for post-spawn mods -} - -void spawnfunc_monster_spider() -{ - if not(autocvar_g_monster_spider) { remove(self); return; } - - self.monster_spawnfunc = spawnfunc_monster_spider; - - if(Monster_CheckAppearFlags(self)) - return; - - if not (monster_initialize( - "Spider", MONSTER_SPIDER, - SPIDER_MIN, SPIDER_MAX, - FALSE, - spider_die, spider_spawn)) - { - remove(self); - return; - } -} - -#endif // SVQC diff --git a/qcsrc/server/monsters/monster/stingray.qc b/qcsrc/server/monsters/monster/stingray.qc deleted file mode 100644 index 61dccfbd6..000000000 --- a/qcsrc/server/monsters/monster/stingray.qc +++ /dev/null @@ -1,95 +0,0 @@ -const vector STINGRAY_MIN = '-20 -20 -31'; -const vector STINGRAY_MAX = '20 20 20'; - -string STINGRAY_MODEL = "models/monsters/fish.mdl"; - -#ifdef SVQC -float autocvar_g_monster_stingray; -float autocvar_g_monster_stingray_health; -float autocvar_g_monster_stingray_damage; -float autocvar_g_monster_stingray_speed_walk; -float autocvar_g_monster_stingray_speed_run; - -const float stingray_anim_attack = 0; -const float stingray_anim_death = 1; -const float stingray_anim_swim = 2; -const float stingray_anim_pain = 3; - -void stingray_think() -{ - self.think = stingray_think; - self.nextthink = time + self.ticrate; - - monster_move(autocvar_g_monster_stingray_speed_run, autocvar_g_monster_stingray_speed_walk, 10, stingray_anim_swim, stingray_anim_swim, stingray_anim_swim); -} - -float stingray_attack(float attack_type) -{ - switch(attack_type) - { - case MONSTER_ATTACK_MELEE: - { - monsters_setframe(stingray_anim_attack); - self.attack_finished_single = time + 0.5; - monster_melee(self.enemy, autocvar_g_monster_stingray_damage, 0.1, DEATH_MONSTER_STINGRAY, FALSE); - - return TRUE; - } - case MONSTER_ATTACK_RANGED: - } - - return FALSE; -} - -void stingray_die() -{ - Monster_CheckDropCvars ("stingray"); - - self.think = monster_dead_think; - self.nextthink = time + self.ticrate; - self.ltime = time + 5; - monsters_setframe(stingray_anim_death); - - monster_hook_death(); // for post-death mods -} - -void stingray_spawn() -{ - if not(self.health) - self.health = autocvar_g_monster_stingray_health; - - self.damageforcescale = 0.5; - self.classname = "monster_stingray"; - self.monster_attackfunc = stingray_attack; - self.flags |= FL_SWIM; - self.nextthink = time + random() * 0.5 + 0.1; - self.think = stingray_think; - - monster_setupsounds("stingray"); - - monster_hook_spawn(); // for post-spawn mods -} - -void spawnfunc_monster_stingray() -{ - if not(autocvar_g_monster_stingray) { remove(self); return; } - - self.monster_spawnfunc = spawnfunc_monster_stingray; - - if(Monster_CheckAppearFlags(self)) - return; - - self.scale = 1.3; - - if not (monster_initialize( - "Stingray", MONSTER_STINGRAY, - STINGRAY_MIN, STINGRAY_MAX, - TRUE, - stingray_die, stingray_spawn)) - { - remove(self); - return; - } -} - -#endif // SVQC diff --git a/qcsrc/server/monsters/monster/wyvern.qc b/qcsrc/server/monsters/monster/wyvern.qc deleted file mode 100644 index e9eda6d6a..000000000 --- a/qcsrc/server/monsters/monster/wyvern.qc +++ /dev/null @@ -1,154 +0,0 @@ -const vector WYVERN_MIN = '-20 -20 -58'; -const vector WYVERN_MAX = '20 20 20'; - -string WYVERN_MODEL = "models/monsters/wizard.mdl"; - -#ifdef SVQC -float autocvar_g_monster_wyvern; -float autocvar_g_monster_wyvern_health; -float autocvar_g_monster_wyvern_speed_walk; -float autocvar_g_monster_wyvern_speed_run; -float autocvar_g_monster_wyvern_fireball_damage; -float autocvar_g_monster_wyvern_fireball_force; -float autocvar_g_monster_wyvern_fireball_radius; -float autocvar_g_monster_wyvern_fireball_edgedamage; -float autocvar_g_monster_wyvern_fireball_damagetime; -float autocvar_g_monster_wyvern_fireball_speed; - -const float wyvern_anim_hover = 0; -const float wyvern_anim_fly = 1; -const float wyvern_anim_magic = 2; -const float wyvern_anim_pain = 3; -const float wyvern_anim_death = 4; - -void wyvern_think() -{ - self.think = wyvern_think; - self.nextthink = time + self.ticrate; - - monster_move(autocvar_g_monster_wyvern_speed_run, autocvar_g_monster_wyvern_speed_walk, 300, wyvern_anim_fly, wyvern_anim_hover, wyvern_anim_hover); -} - -void wyvern_fireball_explode() -{ - entity e; - if(self) - { - pointparticles(particleeffectnum("fireball_explode"), self.origin, '0 0 0', 1); - - RadiusDamage(self, self.realowner, autocvar_g_monster_wyvern_fireball_damage, autocvar_g_monster_wyvern_fireball_edgedamage, autocvar_g_monster_wyvern_fireball_force, world, autocvar_g_monster_wyvern_fireball_radius, self.projectiledeathtype, world); - - for(e = world; (e = findfloat(e, takedamage, DAMAGE_AIM)); ) if(vlen(e.origin - self.origin) <= autocvar_g_monster_wyvern_fireball_radius) - Fire_AddDamage(e, self, 5 * monster_skill, autocvar_g_monster_wyvern_fireball_damagetime, self.projectiledeathtype); - - remove(self); - } -} - -void wyvern_fireball_touch() -{ - PROJECTILE_TOUCH; - - wyvern_fireball_explode(); -} - -void wyvern_fireball() -{ - entity missile = spawn(); - vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin); - - monster_makevectors(self.enemy); - - missile.owner = missile.realowner = self; - missile.solid = SOLID_TRIGGER; - missile.movetype = MOVETYPE_FLYMISSILE; - missile.projectiledeathtype = DEATH_MONSTER_WYVERN; - setsize(missile, '-6 -6 -6', '6 6 6'); - setorigin(missile, self.origin + self.view_ofs + v_forward * 14); - missile.flags = FL_PROJECTILE; - missile.velocity = dir * autocvar_g_monster_wyvern_fireball_speed; - missile.avelocity = '300 300 300'; - missile.nextthink = time + 5; - missile.think = wyvern_fireball_explode; - missile.enemy = self.enemy; - missile.touch = wyvern_fireball_touch; - CSQCProjectile(missile, TRUE, PROJECTILE_FIREMINE, TRUE); -} - -float wyvern_attack(float attack_type) -{ - switch(attack_type) - { - case MONSTER_ATTACK_MELEE: - case MONSTER_ATTACK_RANGED: - { - self.attack_finished_single = time + 1.2; - - wyvern_fireball(); - - return TRUE; - } - } - - return FALSE; -} - -void wyvern_die() -{ - Monster_CheckDropCvars ("wyvern"); - - self.think = monster_dead_think; - self.nextthink = time + self.ticrate; - self.ltime = time + 5; - self.velocity_x = -200 + 400 * random(); - self.velocity_y = -200 + 400 * random(); - self.velocity_z = 100 + 100 * random(); - - monsters_setframe(wyvern_anim_death); - - monster_hook_death(); // for post-death mods -} - -void wyvern_spawn() -{ - if not(self.health) - self.health = autocvar_g_monster_wyvern_health; - - self.classname = "monster_wyvern"; - self.monster_attackfunc = wyvern_attack; - self.nextthink = time + random() * 0.5 + 0.1; - self.movetype = MOVETYPE_FLY; - self.flags |= FL_FLY; - self.think = wyvern_think; - - monster_setupsounds("wyvern"); - - monster_hook_spawn(); // for post-spawn mods -} - -void spawnfunc_monster_wyvern() -{ - if not(autocvar_g_monster_wyvern) { remove(self); return; } - - self.monster_spawnfunc = spawnfunc_monster_wyvern; - - if(Monster_CheckAppearFlags(self)) - return; - - self.scale = 1.3; - - if not (monster_initialize( - "Wyvern", MONSTER_WYVERN, - WYVERN_MIN, WYVERN_MAX, - TRUE, - wyvern_die, wyvern_spawn)) - { - remove(self); - return; - } -} - -// compatibility with old spawns -void spawnfunc_monster_wizard() { spawnfunc_monster_wyvern(); } - -#endif // SVQC diff --git a/qcsrc/server/monsters/monster/zombie.qc b/qcsrc/server/monsters/monster/zombie.qc deleted file mode 100644 index aa96920d7..000000000 --- a/qcsrc/server/monsters/monster/zombie.qc +++ /dev/null @@ -1,165 +0,0 @@ -const vector ZOMBIE_MIN = '-18 -18 -25'; -const vector ZOMBIE_MAX = '18 18 47'; - -string ZOMBIE_MODEL = "models/monsters/zombie.dpm"; - -#ifdef SVQC -float autocvar_g_monster_zombie; -float autocvar_g_monster_zombie_stopspeed; -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_speed; -float autocvar_g_monster_zombie_attack_stand_damage; -float autocvar_g_monster_zombie_attack_stand_delay; -float autocvar_g_monster_zombie_health; -float autocvar_g_monster_zombie_speed_walk; -float autocvar_g_monster_zombie_speed_run; - -const float zombie_anim_attackleap = 0; -const float zombie_anim_attackrun1 = 1; -const float zombie_anim_attackrun2 = 2; -const float zombie_anim_attackrun3 = 3; -const float zombie_anim_attackstanding1 = 4; -const float zombie_anim_attackstanding2 = 5; -const float zombie_anim_attackstanding3 = 6; -const float zombie_anim_blockend = 7; -const float zombie_anim_blockstart = 8; -const float zombie_anim_deathback1 = 9; -const float zombie_anim_deathback2 = 10; -const float zombie_anim_deathback3 = 11; -const float zombie_anim_deathfront1 = 12; -const float zombie_anim_deathfront2 = 13; -const float zombie_anim_deathfront3 = 14; -const float zombie_anim_deathleft1 = 15; -const float zombie_anim_deathleft2 = 16; -const float zombie_anim_deathright1 = 17; -const float zombie_anim_deathright2 = 18; -const float zombie_anim_idle = 19; -const float zombie_anim_painback1 = 20; -const float zombie_anim_painback2 = 21; -const float zombie_anim_painfront1 = 22; -const float zombie_anim_painfront2 = 23; -const float zombie_anim_runbackwards = 24; -const float zombie_anim_runbackwardsleft = 25; -const float zombie_anim_runbackwardsright = 26; -const float zombie_anim_runforward = 27; -const float zombie_anim_runforwardleft = 28; -const float zombie_anim_runforwardright = 29; -const float zombie_anim_spawn = 30; - -void zombie_think() -{ - self.think = zombie_think; - self.nextthink = time + self.ticrate; - - 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_attack_leap_touch() -{ - if (self.health <= 0) - return; - - vector angles_face; - - if(other.takedamage) - { - angles_face = vectoangles(self.moveto - self.origin); - angles_face = normalize(angles_face) * autocvar_g_monster_zombie_attack_leap_force; - Damage(other, self, self, autocvar_g_monster_zombie_attack_leap_damage * monster_skill, DEATH_MONSTER_ZOMBIE_JUMP, other.origin, angles_face); - self.touch = MonsterTouch; // instantly turn it off to stop damage spam - } - - if (trace_dphitcontents) - self.touch = MonsterTouch; -} - -float zombie_attack(float attack_type) -{ - switch(attack_type) - { - case MONSTER_ATTACK_MELEE: - { - float rand = random(), chosen_anim; - - if(rand < 0.33) - chosen_anim = zombie_anim_attackstanding1; - else if(rand < 0.66) - chosen_anim = zombie_anim_attackstanding2; - else - chosen_anim = zombie_anim_attackstanding3; - - monsters_setframe(chosen_anim); - - self.attack_finished_single = time + autocvar_g_monster_zombie_attack_stand_delay; - - monster_melee(self.enemy, autocvar_g_monster_zombie_attack_stand_damage, 0.3, DEATH_MONSTER_ZOMBIE_MELEE, TRUE); - - return TRUE; - } - case MONSTER_ATTACK_RANGED: - { - makevectors(self.angles); - 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_die() -{ - Monster_CheckDropCvars ("zombie"); - - self.think = monster_dead_think; - self.nextthink = time + self.ticrate; - self.ltime = time + 5; - monsters_setframe((random() > 0.5) ? zombie_anim_deathback1 : zombie_anim_deathfront1); - - monster_hook_death(); // for post-death mods -} - -void zombie_spawn() -{ - if not(self.health) - self.health = autocvar_g_monster_zombie_health; - - self.classname = "monster_zombie"; - self.spawn_time = time + 2.1; - self.nextthink = time + random() * 0.5 + 0.1; - self.think = zombie_think; - self.monster_attackfunc = zombie_attack; - self.spawnshieldtime = self.spawn_time; - self.respawntime = 0.1; - self.spawnflags |= MONSTER_RESPAWN_DEATHPOINT; // always enabled for zombie - - monsters_setframe(zombie_anim_spawn); - - monster_setupsounds("zombie"); - - monster_hook_spawn(); // for post-spawn mods -} - -void spawnfunc_monster_zombie() -{ - if not(autocvar_g_monster_zombie) { remove(self); return; } - - self.monster_spawnfunc = spawnfunc_monster_zombie; - - if(Monster_CheckAppearFlags(self)) - return; - - if not (monster_initialize( - "Zombie", MONSTER_ZOMBIE, - ZOMBIE_MIN, ZOMBIE_MAX, - FALSE, - zombie_die, zombie_spawn)) - { - remove(self); - return; - } -} - -#endif //SVQC diff --git a/qcsrc/server/monsters/monsters.qh b/qcsrc/server/monsters/monsters.qh deleted file mode 100644 index 53b5a13ad..000000000 --- a/qcsrc/server/monsters/monsters.qh +++ /dev/null @@ -1,20 +0,0 @@ -// Lib -#ifdef SVQC -#include "lib/defs.qh" -#include "lib/monsters.qc" -#include "lib/spawn.qc" -#endif - -// Monsters -#include "monster/brute.qc" -#include "monster/animus.qc" -#include "monster/shambler.qc" -#include "monster/bruiser.qc" -#include "monster/wyvern.qc" -#include "monster/cerberus.qc" -#include "monster/slime.qc" -#include "monster/knight.qc" -#include "monster/stingray.qc" -#include "monster/mage.qc" -#include "monster/zombie.qc" -#include "monster/spider.qc" diff --git a/qcsrc/server/mutators/gamemode_invasion.qc b/qcsrc/server/mutators/gamemode_invasion.qc index 7b82f8127..47af8e78d 100644 --- a/qcsrc/server/mutators/gamemode_invasion.qc +++ b/qcsrc/server/mutators/gamemode_invasion.qc @@ -8,15 +8,15 @@ void invasion_spawnpoint() float invasion_PickMonster(float have_shamblers) { if(autocvar_g_invasion_zombies_only) - return MONSTER_ZOMBIE; + return MON_ZOMBIE; float i; RandomSelection_Init(); - for(i = MONSTER_FIRST + 1; i < MONSTER_LAST; ++i) + for(i = MON_FIRST; i <= MON_LAST; ++i) { - if(i == MONSTER_STINGRAY || i == MONSTER_WYVERN || (i == MONSTER_SHAMBLER && have_shamblers >= 1)) + if(i == MON_STINGRAY || i == MON_WYVERN || (i == MON_SHAMBLER && have_shamblers >= 1)) continue; // flying/swimming monsters not yet supported RandomSelection_Add(world, i, "", 1, 1); @@ -88,7 +88,7 @@ float Invasion_CheckWinner() FOR_EACH_MONSTER(head) if(head.health > 0) { - if(head.monsterid == MONSTER_SHAMBLER) + if(head.monsterid == MON_SHAMBLER) ++shamblers; ++total_alive_monsters; } diff --git a/qcsrc/server/mutators/mutator_minstagib.qc b/qcsrc/server/mutators/mutator_minstagib.qc index d438faa62..5fae3a0e1 100644 --- a/qcsrc/server/mutators/mutator_minstagib.qc +++ b/qcsrc/server/mutators/mutator_minstagib.qc @@ -119,7 +119,7 @@ MUTATOR_HOOKFUNCTION(minstagib_MonsterLoot) MUTATOR_HOOKFUNCTION(minstagib_MonsterSpawn) { // always refill ammo - if(self.monsterid == MONSTER_MAGE) + if(self.monsterid == MON_MAGE) self.skin = 1; return FALSE; diff --git a/qcsrc/server/progs.src b/qcsrc/server/progs.src index d446d1bd3..7de6565c3 100644 --- a/qcsrc/server/progs.src +++ b/qcsrc/server/progs.src @@ -26,6 +26,7 @@ sys-post.qh ../common/command/shared_defs.qh ../common/net_notice.qh ../common/animdecide.qh +../common/monsters/monsters.qh autocvars.qh constants.qh @@ -53,7 +54,7 @@ mutators/mutator_nades.qh tturrets/include/turrets_early.qh vehicles/vehicles_def.qh -monsters/lib/monsters_early.qh +../common/monsters/lib/monsters_early.qh generator.qh @@ -233,7 +234,7 @@ round_handler.qc ../common/explosion_equation.qc -monsters/monsters.qh +../common/monsters/monsters.qc mutators/base.qc mutators/gamemode_assault.qc