From: Mario Date: Fri, 10 May 2013 06:50:55 +0000 (+1000) Subject: Merge branch 'master' into Mario/monsters X-Git-Tag: xonotic-v0.8.0~241^2^2~245 X-Git-Url: https://git.rm.cloudns.org/?a=commitdiff_plain;h=306b95b7d03edfdada049ad3118809137bfe731d;p=xonotic%2Fxonotic-data.pk3dir.git Merge branch 'master' into Mario/monsters --- 306b95b7d03edfdada049ad3118809137bfe731d diff --cc qcsrc/server/accuracy.qc index fd62ac7ad,3c737f6e6..d4b706dca --- a/qcsrc/server/accuracy.qc +++ b/qcsrc/server/accuracy.qc @@@ -109,11 -109,8 +109,11 @@@ void accuracy_add(entity e, float w, fl float accuracy_isgooddamage(entity attacker, entity targ) { - float targ_isvalid = ((g_td) ? targ.flags & FL_MONSTER : targ.flags & FL_CLIENT); ++ float targ_isvalid = ((g_td) ? targ.flags & FL_MONSTER : IS_CLIENT(targ)); + if(!inWarmupStage) - if(IS_CLIENT(targ)) + if(targ_isvalid) + if not(attacker.flags & FL_MONSTER) // no accuracy for monsters if(targ.deadflag == DEAD_NO) if(IsDifferentTeam(attacker, targ)) return TRUE; diff --cc qcsrc/server/cl_client.qc index 24e7048ae,02839baa2..c6af6f785 --- a/qcsrc/server/cl_client.qc +++ b/qcsrc/server/cl_client.qc @@@ -405,12 -397,8 +397,10 @@@ void PutObserverInServer (void MUTATOR_CALLHOOK(MakePlayerObserver); - minstagib_stop_countdown(self); - Portal_ClearAll(self); + Unfreeze(self); + if(self.alivetime) { if(!inWarmupStage) @@@ -1195,8 -1168,8 +1173,8 @@@ void ClientKill (void { if(gameover) return; if(self.player_blocked) return; - if(self.freezetag_frozen) return; + if(self.frozen) return; - + ClientKill_TeamChange(0); } diff --cc qcsrc/server/g_damage.qc index 8473062c8,da013fc75..91b03454b --- a/qcsrc/server/g_damage.qc +++ b/qcsrc/server/g_damage.qc @@@ -841,7 -726,7 +794,7 @@@ void Damage (entity targ, entity inflic else victim = targ; - if(victim.classname == "player" || victim.turrcaps_flags & TFL_TURRCAPS_ISTURRET || victim.flags & FL_MONSTER) - if(IS_PLAYER(victim) || victim.turrcaps_flags & TFL_TURRCAPS_ISTURRET) ++ if(IS_PLAYER(victim) || victim.turrcaps_flags & TFL_TURRCAPS_ISTURRET || victim.flags & FL_MONSTER) { if(IsDifferentTeam(victim, attacker)) { diff --cc qcsrc/server/miscfunctions.qc index 4b2d366cf,ee993d5ec..db1dca162 --- a/qcsrc/server/miscfunctions.qc +++ b/qcsrc/server/miscfunctions.qc @@@ -1052,6 -998,8 +1000,10 @@@ void readlevelcvars(void g_bugrigs_speed_ref = cvar("g_bugrigs_speed_ref"); g_bugrigs_speed_pow = cvar("g_bugrigs_speed_pow"); g_bugrigs_steer = cvar("g_bugrigs_steer"); + ++ monster_skill = cvar("g_monsters_skill"); ++ + g_minstagib = cvar("g_minstagib"); sv_clones = cvar("sv_clones"); sv_foginterval = cvar("sv_foginterval"); diff --cc qcsrc/server/monsters/lib/monsters.qc index 83621fd00,000000000..6eba15248 mode 100644,000000..100644 --- a/qcsrc/server/monsters/lib/monsters.qc +++ b/qcsrc/server/monsters/lib/monsters.qc @@@ -1,1044 -1,0 +1,1046 @@@ +// 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() +{ + self.monster_delayedattack(); + + 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_delayedattack = spawnfunc_item_armor_large; break; + case "large": e.monster_delayedattack = spawnfunc_item_armor_big; break; + case "medium": e.monster_delayedattack = spawnfunc_item_armor_medium; break; + case "small": e.monster_delayedattack = spawnfunc_item_armor_small; break; + } + break; // break here? + } + case "health": + { + switch(itemsize) + { + case "mega": e.monster_delayedattack = spawnfunc_item_health_mega; break; + case "large": e.monster_delayedattack = spawnfunc_item_health_large; break; + case "medium": e.monster_delayedattack = spawnfunc_item_health_medium; break; + case "small": e.monster_delayedattack = spawnfunc_item_health_small; break; + } + break; // break here? + } + case "ammo": + { + switch(itemsize) + { + case "shells": e.monster_delayedattack = spawnfunc_item_shells; break; + case "cells": e.monster_delayedattack = spawnfunc_item_cells; break; + case "rockets": e.monster_delayedattack = spawnfunc_item_rockets; break; + case "bullets": + case "nails": e.monster_delayedattack = spawnfunc_item_bullets; break; + } + break; + } + } + + if(g_minstagib) + e.monster_delayedattack = spawnfunc_item_minst_cells; + + 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 targetted + + 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")); +} + +void monster_melee (entity targ, float damg, float er, float deathtype, float dostop) +{ + float dot, rdmg = damg * random(); + + if (self.health <= 0) + return; + if (targ == world) + return; + + 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)); +} + +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")); + + monster_dropitem = dropitem; + monster_dropsize = dropsize; + MUTATOR_CALLHOOK(MonsterDropItem); + dropitem = monster_dropitem; + dropsize = monster_dropsize; + + 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) + { + e.monster_delayedattack = func_null; + e.delay = -1; + return; + } + + if not(e.monster_attackfunc) + return; + + if(e.monster_delayedattack && e.delay != -1) + { + if(time < e.delay) + return; + + e.monster_delayedattack(); + + 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 = e.origin + (e.mins + e.maxs) * 0.5; + 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; +vector monster_pickmovetarget(entity targ) +{ + // enemy is always preferred target + if(self.enemy) + { + self.monster_movestate = MONSTER_MOVE_ENEMY; + self.last_trace = time + 0.1; + 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 != "monster_swarm") + 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) +{ + 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 not(monster_isvalidtarget(self.enemy, self)) + self.enemy = world; // check enemy each think frame? + + 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); + + vector angles_face = vectoangles(self.moveto - self.origin); + vector owner_face = vectoangles(self.monster_owner.origin - self.origin); + vector enemy_face = vectoangles(self.enemy.origin - self.origin); + + if(!(self.flags & FL_FLY || self.flags & FL_SWIM)) + self.moveto_z = self.origin_z; + + if(self.state != MONSTER_STATE_ATTACK_LEAP) + self.angles_y = angles_face_y; + + if(self.state == MONSTER_STATE_ATTACK_LEAP && (self.flags & FL_ONGROUND)) + { + self.state = 0; + self.touch = MonsterTouch; + } + + v_forward = normalize(self.moveto - self.origin); + + 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(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 + { + if(targ.target) + self.target2 = targ.target; + else if(targ.target2) + self.target2 = targ.target2; + else + { + movelib_beak_simple(stopspeed); + if(time > self.attack_finished_single) + if(time > self.pain_finished) + if (vlen(self.velocity) <= 30) + { + monsters_setframe(manim_idle); + if(self.enemy) + self.angles_y = enemy_face_y; + else + self.angles_y = ((self.monster_owner) ? owner_face_y : self.pos2_y); // reset looking angle now? + } + } + } + + if(self.enemy) + 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((ignore_turrets && !(attacker.turrcaps_flags & TFL_TURRCAPS_ISTURRET)) || !ignore_turrets) + if(monster_isvalidtarget(attacker, self)) + self.enemy = attacker; + + 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; +} + +// used to hook into monster post death functions without a mutator +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; +} + +// used to hook into monster post spawn functions without a mutator +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) + 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.delay = -1; // used in attack delay code + self.spawnshieldtime = time + autocvar_g_monster_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.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 --cc qcsrc/server/mutators/gamemode_ctf.qc index 1efc97192,bc19d88eb..5b849930a --- a/qcsrc/server/mutators/gamemode_ctf.qc +++ b/qcsrc/server/mutators/gamemode_ctf.qc @@@ -832,12 -825,7 +832,12 @@@ void ctf_FlagTouch( else return; // do nothing } + else if(toucher.flags & FL_MONSTER) + { + if not(autocvar_g_ctf_allow_monster_touch) + return; // do nothing + } - else if(toucher.classname != "player") // The flag just touched an object, most likely the world + else if not(IS_PLAYER(toucher)) // The flag just touched an object, most likely the world { if(time > self.wait) // if we haven't in a while, play a sound/effect { diff --cc qcsrc/server/t_items.qc index e8b183b8f,2cc9e1f43..c9e671108 --- a/qcsrc/server/t_items.qc +++ b/qcsrc/server/t_items.qc @@@ -762,10 -682,8 +682,10 @@@ void Item_Touch (void } } - if (other.classname != "player") + if not(IS_PLAYER(other)) return; + if (other.frozen) + return; if (other.deadflag) return; if (self.solid != SOLID_TRIGGER) diff --cc qcsrc/server/tturrets/system/system_scoreprocs.qc index 16ecbce5e,539be2ad9..223f8eacf --- a/qcsrc/server/tturrets/system/system_scoreprocs.qc +++ b/qcsrc/server/tturrets/system/system_scoreprocs.qc @@@ -44,10 -44,7 +44,10 @@@ float turret_stdproc_targetscore_generi if ((_turret.target_select_missilebias > 0) && (_target.flags & FL_PROJECTILE)) m_score = 1; - if ((_turret.target_select_playerbias > 0) && (_target.flags & FL_CLIENT) && !g_td) - if ((_turret.target_select_playerbias > 0) && IS_CLIENT(_target)) ++ if ((_turret.target_select_playerbias > 0) && IS_CLIENT(_target) && !g_td) + p_score = 1; + + if(g_td && _target.flags & FL_MONSTER) p_score = 1; d_score = max(d_score, 0); diff --cc qcsrc/server/vehicles/vehicles.qc index 34fccdee3,40c90943e..f1dcf93f0 --- a/qcsrc/server/vehicles/vehicles.qc +++ b/qcsrc/server/vehicles/vehicles.qc @@@ -633,9 -633,8 +633,9 @@@ void vehicles_enter( self.team = self.owner.team; self.flags -= FL_NOTARGET; + self.monster_attack = TRUE; - if (clienttype(other) == CLIENTTYPE_REAL) + if (IS_REAL_CLIENT(other)) { msg_entity = other; WriteByte (MSG_ONE, SVC_SETVIEWPORT);