From: Mario Date: Thu, 29 Aug 2013 12:40:39 +0000 (+1000) Subject: Clean up filenames a bit X-Git-Tag: xonotic-v0.8.0~241^2^2~138 X-Git-Url: https://git.rm.cloudns.org/?a=commitdiff_plain;h=9a66589d6acbd35df28b897d603709b435b78710;p=xonotic%2Fxonotic-data.pk3dir.git Clean up filenames a bit --- diff --git a/qcsrc/client/monsters.qc b/qcsrc/client/monsters.qc deleted file mode 100644 index 0dfbcf4c0..000000000 --- a/qcsrc/client/monsters.qc +++ /dev/null @@ -1,141 +0,0 @@ -.vector glowmod; -void monster_changeteam() -{ - self.glowmod = Team_ColorRGB(self.team - 1); - self.teamradar_color = Team_ColorRGB(self.team - 1); - - if(self.team) - self.colormap = 1024 + (self.team - 1) * 17; - else - self.colormap = 1024; -} - -void monster_die() -{ - MON_ACTION(self.monsterid, MR_DEATH); - - self.solid = SOLID_CORPSE; -} - -void monster_draw() -{ - float dt; - - dt = time - self.move_time; - self.move_time = time; - if(dt <= 0) - return; - - fixedmakevectors(self.angles); - //movelib_groundalign4point(50, 25, 0.25, 45); - setorigin(self, self.origin + self.velocity * dt); - self.angles_y = self.move_angles_y; -} - -void monster_construct() -{ - 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, mon.model); - setsize(self, min_s, max_s); - - self.move_movetype = MOVETYPE_BOUNCE; - self.health = 255; - self.solid = SOLID_BBOX; - self.movetype = MOVETYPE_BOUNCE; - self.move_origin = self.origin; - self.move_time = time; - self.drawmask = MASK_NORMAL; - self.alpha = 1; - self.draw = monster_draw; -} - -void ent_monster() -{ - float sf; - sf = ReadByte(); - - if(sf & MSF_SETUP) - { - self.monsterid = ReadByte(); - - self.origin_x = ReadCoord(); - self.origin_y = ReadCoord(); - self.origin_z = ReadCoord(); - setorigin(self, self.origin); - - self.angles_x = ReadAngle(); - self.angles_y = ReadAngle(); - - self.skin = ReadByte(); - self.team = ReadByte(); - - monster_construct(); - monster_changeteam(); - } - - if(sf & MSF_ANG) - { - self.move_angles_x = ReadShort(); - self.move_angles_y = ReadShort(); - self.angles = self.move_angles; - } - - if(sf & MSF_MOVE) - { - self.origin_x = ReadShort(); - self.origin_y = ReadShort(); - self.origin_z = ReadShort(); - setorigin(self, self.origin); - - self.velocity_x = ReadShort(); - self.velocity_y = ReadShort(); - self.velocity_z = ReadShort(); - - self.move_angles_y = ReadShort(); - - self.move_time = time; - self.move_velocity = self.velocity; - self.move_origin = self.origin; - } - - if(sf & MSF_ANIM) - { - self.frame1time = ReadCoord(); - self.frame = ReadByte(); - } - - if(sf & MSF_STATUS) - { - self.skin = ReadByte(); - - float _tmp; - _tmp = ReadByte(); - if(_tmp != self.team) - { - self.team = _tmp; - monster_changeteam(); - } - - _tmp = ReadByte(); - if(_tmp == 4) // respawning - setmodel(self, "null"); - - _tmp = ReadByte(); - - if(_tmp == 0 && self.health != 0) - monster_die(); - - self.health = _tmp; - } -} diff --git a/qcsrc/client/monsters.qh b/qcsrc/client/monsters.qh deleted file mode 100644 index aa6fb4126..000000000 --- a/qcsrc/client/monsters.qh +++ /dev/null @@ -1 +0,0 @@ -void ent_monster(); diff --git a/qcsrc/client/progs.src b/qcsrc/client/progs.src index bc1ec35eb..0b68e7e96 100644 --- a/qcsrc/client/progs.src +++ b/qcsrc/client/progs.src @@ -27,9 +27,11 @@ Defs.qc ../common/command/shared_defs.qh ../common/urllib.qh ../common/animdecide.qh -../common/monsters/monsters.qh command/cl_cmd.qh +../common/monsters/cl_monsters.qh +../common/monsters/monsters.qh + autocvars.qh ../common/notifications.qh // must be after autocvars @@ -48,8 +50,6 @@ bgmscript.qh noise.qh tturrets.qh ../server/tturrets/include/turrets_early.qh -monsters.qh -../common/monsters/lib/monsters_early.qh ../server/movelib.qc ../server/generator.qh main.qh @@ -115,9 +115,11 @@ noise.qc ../server/w_all.qc ../common/explosion_equation.qc ../common/urllib.qc -../common/monsters/monsters.qc command/cl_cmd.qc +../common/monsters/cl_monsters.qc +../common/monsters/monsters.qc + ../warpzonelib/anglestransform.qc ../warpzonelib/mathlib.qc ../warpzonelib/common.qc @@ -126,8 +128,6 @@ tturrets.qc ../server/generator.qc -monsters.qc - player_skeleton.qc ../common/animdecide.qc diff --git a/qcsrc/common/monsters/cl_monsters.qc b/qcsrc/common/monsters/cl_monsters.qc new file mode 100644 index 000000000..869842c8a --- /dev/null +++ b/qcsrc/common/monsters/cl_monsters.qc @@ -0,0 +1,146 @@ +// ========================= +// CSQC Monster Properties +// ========================= + + +.vector glowmod; +void monster_changeteam() +{ + self.glowmod = Team_ColorRGB(self.team - 1); + self.teamradar_color = Team_ColorRGB(self.team - 1); + + if(self.team) + self.colormap = 1024 + (self.team - 1) * 17; + else + self.colormap = 1024; +} + +void monster_die() +{ + MON_ACTION(self.monsterid, MR_DEATH); + + self.solid = SOLID_CORPSE; +} + +void monster_draw() +{ + float dt; + + dt = time - self.move_time; + self.move_time = time; + if(dt <= 0) + return; + + fixedmakevectors(self.angles); + //movelib_groundalign4point(50, 25, 0.25, 45); + setorigin(self, self.origin + self.velocity * dt); + self.angles_y = self.move_angles_y; +} + +void monster_construct() +{ + 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, mon.model); + setsize(self, min_s, max_s); + + self.move_movetype = MOVETYPE_BOUNCE; + self.health = 255; + self.solid = SOLID_BBOX; + self.movetype = MOVETYPE_BOUNCE; + self.move_origin = self.origin; + self.move_time = time; + self.drawmask = MASK_NORMAL; + self.alpha = 1; + self.draw = monster_draw; +} + +void ent_monster() +{ + float sf; + sf = ReadByte(); + + if(sf & MSF_SETUP) + { + self.monsterid = ReadByte(); + + self.origin_x = ReadCoord(); + self.origin_y = ReadCoord(); + self.origin_z = ReadCoord(); + setorigin(self, self.origin); + + self.angles_x = ReadAngle(); + self.angles_y = ReadAngle(); + + self.skin = ReadByte(); + self.team = ReadByte(); + + monster_construct(); + monster_changeteam(); + } + + if(sf & MSF_ANG) + { + self.move_angles_x = ReadShort(); + self.move_angles_y = ReadShort(); + self.angles = self.move_angles; + } + + if(sf & MSF_MOVE) + { + self.origin_x = ReadShort(); + self.origin_y = ReadShort(); + self.origin_z = ReadShort(); + setorigin(self, self.origin); + + self.velocity_x = ReadShort(); + self.velocity_y = ReadShort(); + self.velocity_z = ReadShort(); + + self.move_angles_y = ReadShort(); + + self.move_time = time; + self.move_velocity = self.velocity; + self.move_origin = self.origin; + } + + if(sf & MSF_ANIM) + { + self.frame1time = ReadCoord(); + self.frame = ReadByte(); + } + + if(sf & MSF_STATUS) + { + self.skin = ReadByte(); + + float _tmp; + _tmp = ReadByte(); + if(_tmp != self.team) + { + self.team = _tmp; + monster_changeteam(); + } + + _tmp = ReadByte(); + if(_tmp == 4) // respawning + setmodel(self, "null"); + + _tmp = ReadByte(); + + if(_tmp == 0 && self.health != 0) + monster_die(); + + self.health = _tmp; + } +} diff --git a/qcsrc/common/monsters/cl_monsters.qh b/qcsrc/common/monsters/cl_monsters.qh new file mode 100644 index 000000000..aa6fb4126 --- /dev/null +++ b/qcsrc/common/monsters/cl_monsters.qh @@ -0,0 +1 @@ +void ent_monster(); diff --git a/qcsrc/common/monsters/lib/defs.qh b/qcsrc/common/monsters/lib/defs.qh deleted file mode 100644 index 1f5fe1293..000000000 --- a/qcsrc/common/monsters/lib/defs.qh +++ /dev/null @@ -1,51 +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; - -.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 deleted file mode 100644 index e423b55ed..000000000 --- a/qcsrc/common/monsters/lib/monsters.qc +++ /dev/null @@ -1,1054 +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() -{ - if(!self.candrop || !self.monster_loot) - return; - - vector org = self.origin + ((self.mins + self.maxs) * 0.5); - entity e = spawn(); - - setorigin(e, org); - - e.monster_loot = self.monster_loot; - - other = e; - MUTATOR_CALLHOOK(MonsterDropItem); - e = other; - - e.think = monster_item_spawn; - e.nextthink = time + 0.3; -} - -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(ent.monster_owner == targ) - return FALSE; // don't attack our master - - if(targ.monster_owner == ent) - return FALSE; // don't attack our pet - - 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_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; - 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(teamplay) - if(autocvar_g_monsters_teams) - 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) - if(vlen(self.velocity) > 0) - monsters_setframe((self.enemy) ? manim_run : manim_walk); - else - monsters_setframe(manim_idle); - } - 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_dropitem(); - - 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(mon.spawnflags & MONSTER_SIZE_BROKEN) - self.scale = 1.3; - - 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 deleted file mode 100644 index f690cdc59..000000000 --- a/qcsrc/common/monsters/lib/monsters_early.qh +++ /dev/null @@ -1,35 +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 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 deleted file mode 100644 index 95e0261ae..000000000 --- a/qcsrc/common/monsters/lib/spawn.qc +++ /dev/null @@ -1,65 +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 != "") - { - 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/monsters.qc b/qcsrc/common/monsters/monsters.qc index 4320f62e8..7f43df51f 100644 --- a/qcsrc/common/monsters/monsters.qc +++ b/qcsrc/common/monsters/monsters.qc @@ -1,9 +1,3 @@ -#ifdef SVQC -#include "lib/defs.qh" -#include "lib/monsters.qc" -#include "lib/spawn.qc" -#endif - #include "all.qh" // MONSTER PLUGIN SYSTEM @@ -50,4 +44,4 @@ entity get_monsterinfo(float id) if(m) return m; return dummy_monster_info; -} +} \ No newline at end of file diff --git a/qcsrc/common/monsters/monsters.qh b/qcsrc/common/monsters/monsters.qh index ae994ae1b..0c9d4b888 100644 --- a/qcsrc/common/monsters/monsters.qh +++ b/qcsrc/common/monsters/monsters.qh @@ -24,6 +24,20 @@ const float MON_FLAG_SUPERMONSTER = 2048; // incredibly powerful monster .string model; // full name of model .float spawnflags; +// csqc linking +#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 + // other useful macros #define MON_ACTION(monstertype,mrequest) (get_monsterinfo(monstertype)).monster_func(mrequest) #define M_NAME(monstertype) (get_monsterinfo(monstertype)).monster_name diff --git a/qcsrc/common/monsters/spawn.qc b/qcsrc/common/monsters/spawn.qc new file mode 100644 index 000000000..95e0261ae --- /dev/null +++ b/qcsrc/common/monsters/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/spawn.qh b/qcsrc/common/monsters/spawn.qh new file mode 100644 index 000000000..7d8410368 --- /dev/null +++ b/qcsrc/common/monsters/spawn.qh @@ -0,0 +1 @@ +entity spawnmonster (string monster, float mnster, entity spawnedby, entity own, vector orig, float respwn, float moveflag); diff --git a/qcsrc/common/monsters/sv_monsters.qc b/qcsrc/common/monsters/sv_monsters.qc new file mode 100644 index 000000000..0f81f283e --- /dev/null +++ b/qcsrc/common/monsters/sv_monsters.qc @@ -0,0 +1,1055 @@ +// ========================= +// SVQC Monster Properties +// ========================= + + +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() +{ + if(!self.candrop || !self.monster_loot) + return; + + vector org = self.origin + ((self.mins + self.maxs) * 0.5); + entity e = spawn(); + + setorigin(e, org); + + e.monster_loot = self.monster_loot; + + other = e; + MUTATOR_CALLHOOK(MonsterDropItem); + e = other; + + e.think = monster_item_spawn; + e.nextthink = time + 0.3; +} + +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(ent.monster_owner == targ) + return FALSE; // don't attack our master + + if(targ.monster_owner == ent) + return FALSE; // don't attack our pet + + 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_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; + 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(teamplay) + if(autocvar_g_monsters_teams) + 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) + if(vlen(self.velocity) > 0) + monsters_setframe((self.enemy) ? manim_run : manim_walk); + else + monsters_setframe(manim_idle); + } + 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_dropitem(); + + 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(mon.spawnflags & MONSTER_SIZE_BROKEN) + self.scale = 1.3; + + 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/sv_monsters.qh b/qcsrc/common/monsters/sv_monsters.qh new file mode 100644 index 000000000..0ceb95852 --- /dev/null +++ b/qcsrc/common/monsters/sv_monsters.qh @@ -0,0 +1,68 @@ +.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 + +.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/server/progs.src b/qcsrc/server/progs.src index 32b61aac0..b8ce21ec2 100644 --- a/qcsrc/server/progs.src +++ b/qcsrc/server/progs.src @@ -29,6 +29,8 @@ sys-post.qh ../common/monsters/config.qh ../common/monsters/monsters.qh +../common/monsters/sv_monsters.qh +../common/monsters/spawn.qh autocvars.qh @@ -57,8 +59,6 @@ mutators/mutator_nades.qh tturrets/include/turrets_early.qh vehicles/vehicles_def.qh -../common/monsters/lib/monsters_early.qh - generator.qh campaign.qh @@ -237,9 +237,12 @@ round_handler.qc ../common/explosion_equation.qc +../common/monsters/sv_monsters.qc ../common/monsters/config.qc ../common/monsters/monsters.qc +../common/monsters/spawn.qc + mutators/base.qc mutators/gamemode_assault.qc mutators/gamemode_arena.qc