/* * Copyright (c) 2016-2023 Marco Cawthorne * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /*QUAKED monster_scientist (0 0.8 0.8) (-16 -16 0) (16 16 72) HALF-LIFE (1998) ENTITY Scientist */ enum { SCIA_WALK, SCIA_WALKSCARED, SCIA_RUN, SCIA_RUNSCARED, SCIA_RUNLOOK, SCIA_180LEFT, SCIA_180RIGHT, SCIA_FLINCH, SCIA_PAIN, SCIA_PAINLEFT, SCIA_PAINRIGHT, SCIA_PAINLEGLEFT, SCIA_PAINLEGRIGHT, SCIA_IDLE1, SCIA_IDLE2, SCIA_IDLE3, SCIA_IDLE4, SCIA_IDLE5, SCIA_IDLE6, SCIA_SCARED_END, SCIA_SCARED1, SCIA_SCARED2, SCIA_SCARED3, SCIA_SCARED4, SCIA_PANIC, SCIA_FEAR1, SCIA_FEAR2, SCIA_CRY, SCIA_SCI1, SCIA_SCI2, SCIA_SCI3, SCIA_DIE_SIMPLE, SCIA_DIE_FORWARD1, SCIA_DIE_FORWARD2, SCIA_DIE_BACKWARD, SCIA_DIE_HEADSHOT, SCIA_DIE_GUTSHOT, SCIA_LYING1, SCIA_LYING2, SCIA_DEADSIT, SCIA_DEADTABLE1, SCIA_DEADTABLE2, SCIA_DEADTABLE3 }; class monster_scientist:NSTalkMonster { void(void) monster_scientist; /* override */ virtual void(void) SeeThink; virtual float(void) GetWalkSpeed; virtual float(void) GetChaseSpeed; virtual float(void) GetRunSpeed; virtual void(void) PanicFrame; virtual void(void) Spawned; virtual void(void) Respawn; virtual void(void) Pain; virtual void(void) Death; virtual void(void) PlayerUse; virtual void(void) TalkPanic; virtual int(void) AnimIdle; virtual int(void) AnimWalk; virtual int(void) AnimRun; virtual int(void) AttackMelee; virtual void(void) AttackNeedle; virtual void(void) FallNoise; virtual void(string, string) SpawnKey; }; /* Players scare scientists if they see them in Stealth Hunting */ void monster_scientist::SeeThink(void) { /* let's call the original monster SeeThink function */ super::SeeThink(); /* don't do anything if they're already scared */ if (m_iFlags & MONSTER_FEAR) return; /* don't do anything if we're in the wrong gamemode */ if not (g_chosen_mode == SHMODE_STEALTH) return; /* iterate over all players */ for (entity e = world; (e = find(e, ::classname, "player"));) { /* is that player visible? then scare the scientist! */ if (Visible(e)) { m_iFlags |= MONSTER_FEAR; return; } } } /* scientist's speed is controlled via cvar */ void monster_scientist::PanicFrame(void) { super::PanicFrame(); input_movevalues = [6 * cvar("sh_scispeed"), 0, 0]; } float monster_scientist::GetWalkSpeed(void) { super::GetWalkSpeed(); return 1.6 * cvar("sh_scispeed"); } float monster_scientist::GetChaseSpeed(void) { super:: GetChaseSpeed(); return 6 * cvar("sh_scispeed"); } float monster_scientist::GetRunSpeed(void) { super::GetRunSpeed(); return 3.5 * cvar("sh_scispeed"); } void monster_scientist::FallNoise(void) { Sound_Speak(this, "monster_scientist.fall"); } int monster_scientist::AttackMelee(void) { /* visual */ AnimPlay(28); m_flAttackThink = m_flAnimTime; float r = random(); /* make them say something extremely fitting, thanks eukara */ if (r < 0.33) Sentence("!SC_CUREA"); else if (r < 0.66) Sentence("!SC_CUREB"); else Sentence("!SC_CUREC"); /* functional */ ScheduleThink(AttackNeedle, 0.25f); return (1); } /* set these globals for scientist's poison * a little messy but it works */ .NSTimer poisonTimer; .entity poisonSource; /* a function for poison that slowly kills the target */ static void monster_scientist_NeedleAttack(entity target) { bool isDead = false; Damage_Apply(target, target.poisonSource, 10, 0, DMG_POISON); /* since corpses have "health" do an extra check */ if (target.health <= 0) isDead = true; if (target.solid == SOLID_CORPSE) isDead = true; if (isDead) { /* the attacker laughs at a sucessful kill if they're not dead * TODO Sound_Speak needs to have an override speach option */ if (target.poisonSource.solid != SOLID_CORPSE) { Sound_Speak(target.poisonSource, "monster_scientist.laugh"); } target.poisonTimer.StopTimer(); } } void monster_scientist::AttackNeedle(void) { /* implement our special function so we know who are we attacking */ static void AttackNeedle_PoisonDamage(void) { monster_scientist_NeedleAttack(self); } /* look for our victim */ traceline(origin, m_eEnemy.origin, FALSE, this); /* if the entity can't take damage, don't bother */ if (trace_fraction >= 1.0 || trace_ent.takedamage != DAMAGE_YES) { return; } /* set the timer for the poison * flag the vitcim so they aren't attacked again (unless Invasion) */ if not (g_chosen_mode == SHMODE_INVASION) { trace_ent.flags |= FL_NOTARGET; } trace_ent.poisonSource = this; trace_ent.poisonTimer = trace_ent.poisonTimer.ScheduleTimer(trace_ent, AttackNeedle_PoisonDamage, 3.0f, true); /* apply our poison attack to the victim */ monster_scientist_NeedleAttack(trace_ent); /* visual */ AnimPlay(30); } /* TODO someday these will use the ACT system */ int monster_scientist::AnimIdle(void) { return SCIA_IDLE1; } int monster_scientist::AnimWalk(void) { return SCIA_WALK; } int monster_scientist::AnimRun(void) { return SCIA_RUN; } void monster_scientist::TalkPanic(void) { /* it's annoying and prevents the laugh in these gamemodes */ if (g_chosen_mode == SHMODE_MADNESS || g_chosen_mode == SHMODE_INVASION) return; int r = floor(random(0,30)); switch (r) { case 1: Sentence("!SC_SCARED1"); //scientist/stopattacking break; case 2: Sentence("!SC_SCARED2"); //scientist/youinsane break; case 3: Sentence("!SC_PLFEAR0"); //scientist/whatyoudoing break; case 4: Sentence("!SC_PLFEAR2"); //scientist/madness break; case 5: Sentence("!SC_PLFEAR3"); //scientist/noplease break; case 6: Sentence("!SC_PLFEAR4"); //scientist/getoutofhere break; case 7: Sentence("!SC_FEAR3"); //scientist/dontwantdie break; case 8: Sentence("!SC_FEAR4"); //scientist/getoutalive break; case 9: Sentence("!SC_FEAR5"); //scientist/startle3 break; case 10: Sentence("!SC_FEAR6"); //scientist/startle4 break; case 11: Sentence("!SC_FEAR7"); //scientist/startle5 break; case 12: Sentence("!SC_FEAR8"); //scientist/startle6 break; case 13: Sentence("!SC_FEAR9"); //scientist/startle7 break; case 14: Sentence("!SC_FEAR10"); //scientist/startle8 break; case 15: Sentence("!SC_FEAR11"); //scientist/startle9 break; case 16: Sentence("!SC_FEAR12"); //scientist/startle1 break; default: Sentence("!SC_SCREAM"); //scientist/sci_fear15 } m_flNextSentence = time + 2.0 + random(0,3); } void monster_scientist::PlayerUse(void) { if (spawnflags & MSF_PREDISASTER) { Sentence("!SC_POK"); return; } if (m_iFlags & MONSTER_FEAR) { bprint(PRINT_HIGH, sprintf("I'm not following you evil person!\n")); return; } super::OnPlayerUse(); } void monster_scientist::Pain(void) { /* make everyone on edge */ WarnAllies(); if (m_flAnimTime > time) { return; } if (random() < 0.25f) { return; } if (IsAlive() == true) { Sound_Speak(this, "monster_scientist.pain"); SetFrame(SCIA_FLINCH + floor(random(0, 6))); m_iFlags |= MONSTER_FEAR; m_flAnimTime = time + 0.25f; } } void monster_scientist::Death(void) { bool deathcheck = false; HLGameRules rules = (HLGameRules)g_grMode; /* upset everyone */ if not (g_chosen_mode == SHMODE_MADNESS || g_chosen_mode == SHMODE_INVASION) StartleAllies(); if (IsAlive() == true) { SetFrame(SCIA_DIE_SIMPLE + floor(random(0, 6))); rules.ScientistKill((player)g_dmg_eAttacker, (entity)this); Plugin_PlayerObituary(g_dmg_eAttacker, this, g_dmg_iWeapon, g_dmg_iHitBody, g_dmg_iDamage); Sound_Speak(this, "monster_scientist.die"); deathcheck = true; } /* now mark our state as 'dead' */ super::Death(); /* now we'll tell our kill function about it, since we're now legally dead */ if (deathcheck == true) { rules.RegisterSciDeath(); } /* will not respawn by themselves in these modes */ if (g_chosen_mode == SHMODE_STANDARD || g_chosen_mode == SHMODE_STEALTH || g_chosen_mode == SHMODE_INVASION) return; ScheduleThink(Respawn, 10.0f); } void monster_scientist::Respawn(void) { HLGameRules rules = (HLGameRules)g_grMode; /* don't spawn if we're hitting scimax */ if (serverkeyfloat("sci_count") >= serverkeyfloat("sv_scimax")) return; super::Respawn(); /* unset notarget for attacking scientists * TODO in the future we shouldn't have to mess with flags this way */ flags &= ~FL_NOTARGET; if not (g_chosen_mode == SHMODE_MADNESS || g_chosen_mode == SHMODE_INVASION) m_iFlags |= MONSTER_CANFOLLOW; /* attack EVERYONE */ if (g_chosen_mode == SHMODE_MADNESS) m_iAlliance = MAL_ROGUE; /* attack anyone but aliens */ if (g_chosen_mode == SHMODE_INVASION) m_iAlliance = MAL_ALIEN; /* scientists are always afraid in standard hunting * and scialert mode */ if (autocvar_sh_scialert || g_chosen_mode == SHMODE_STANDARD) { m_iFlags |= MONSTER_FEAR; } m_iBody = floor(random(1,5)); SetSkin(0); switch (m_iBody) { case 1: m_flPitch = 105; netname = "Walter"; break; case 2: m_flPitch = 100; netname = "Einstein"; break; case 3: m_flPitch = 95; netname = "Luther"; SetSkin(1); break; default: m_flPitch = 100; netname = "Slick"; } /* recount to update sciscore and so on */ rules.CountScientists(); } void monster_scientist::SpawnKey(string strKey, string strValue) { switch (strKey) { case "body": SetBody(stoi(strValue) + 1); break; default: super::SpawnKey(strKey, strValue); } } void monster_scientist::Spawned(void) { if (spawnflags & MSF_PREDISASTER) { m_talkAsk = ""; m_talkPlayerAsk = "!SC_PQUEST"; m_talkPlayerGreet = "!SC_PHELLO"; m_talkPlayerIdle = "!SC_PIDLE"; } else { m_talkAsk = "!SC_QUESTION"; m_talkPlayerAsk = "!SC_QUESTION"; m_talkPlayerGreet = "!SC_HELLO"; m_talkPlayerIdle = "!SC_PIDLE"; } m_talkAnswer = "!SC_ANSWER"; m_talkAllyShot = "!SC_PLFEAR"; m_talkGreet = "!SC_HELLO"; m_talkIdle = "!SC_IDLE"; m_talkHearing = "!SC_HEAR"; m_talkSmelling = "!SC_SMELL"; m_talkStare = "!SC_STARE"; m_talkSurvived = "!SC_WOUND"; m_talkWounded = "!SC_MORTAL"; /* they seem to use predisaster lines regardless of disaster state */ m_talkPlayerWounded1 = "!SC_CUREA"; m_talkPlayerWounded2 = "!SC_CUREB"; m_talkPlayerWounded3 = "!SC_CUREC"; m_talkUnfollow = "!SC_WAIT"; m_talkFollow = "!SC_OK"; m_talkStopFollow = "!SC_STOP"; m_iBody = -1; model = "models/scientist.mdl"; base_mins = [-16,-16,0]; base_maxs = [16,16,72]; base_health = Skill_GetValue("scientist_health", 20); super::Spawned(); Sound_Precache("monster_scientist.die"); Sound_Precache("monster_scientist.pain"); Sound_Precache("monster_scientist.laugh"); Sound_Precache("monster_scientist.fall"); /* has the body not been overriden, etc. choose a character for us */ if (m_iBody == -1) { SetBody((int)floor(random(1,5))); } switch (m_iBody) { case 1: m_flPitch = 105; netname = "Walter"; break; case 2: m_flPitch = 100; netname = "Einstein"; break; case 3: m_flPitch = 95; netname = "Luther"; SetSkin(1); break; default: m_flPitch = 100; netname = "Slick"; } } void monster_scientist::monster_scientist(void) { }