/*** * * Copyright (c) 1996-2001, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. * All Rights Reserved. * * Use, distribution, and modification of this source code and/or resulting * object code is restricted to non-commercial enhancements to products from * Valve LLC. All other use, distribution, or modification is prohibited * without written permission from Valve LLC. * ****/ #include "extdll.h" #include "util.h" #include "cbase.h" #include "animation.h" #include "effects.h" #define XEN_PLANT_GLOW_SPRITE "sprites/flare3.spr" #define XEN_PLANT_HIDE_TIME 5 class CActAnimating : public CBaseAnimating { public: void SetActivity(Activity act); inline Activity GetActivity() { return m_Activity; } int ObjectCaps() override { return CBaseAnimating::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } bool Save(CSave& save) override; bool Restore(CRestore& restore) override; static TYPEDESCRIPTION m_SaveData[]; private: Activity m_Activity; }; TYPEDESCRIPTION CActAnimating::m_SaveData[] = { DEFINE_FIELD(CActAnimating, m_Activity, FIELD_INTEGER), }; IMPLEMENT_SAVERESTORE(CActAnimating, CBaseAnimating); void CActAnimating::SetActivity(Activity act) { int sequence = LookupActivity(act); if (sequence != ACTIVITY_NOT_AVAILABLE) { pev->sequence = sequence; m_Activity = act; pev->frame = 0; ResetSequenceInfo(); } } class CXenPLight : public CActAnimating { public: void Spawn() override; void Precache() override; void Touch(CBaseEntity* pOther) override; void Think() override; void LightOn(); void LightOff(); bool Save(CSave& save) override; bool Restore(CRestore& restore) override; static TYPEDESCRIPTION m_SaveData[]; private: CSprite* m_pGlow; }; LINK_ENTITY_TO_CLASS(xen_plantlight, CXenPLight); TYPEDESCRIPTION CXenPLight::m_SaveData[] = { DEFINE_FIELD(CXenPLight, m_pGlow, FIELD_CLASSPTR), }; IMPLEMENT_SAVERESTORE(CXenPLight, CActAnimating); void CXenPLight::Spawn() { Precache(); SET_MODEL(ENT(pev), "models/light.mdl"); pev->movetype = MOVETYPE_NONE; pev->solid = SOLID_TRIGGER; UTIL_SetSize(pev, Vector(-80, -80, 0), Vector(80, 80, 32)); SetActivity(ACT_IDLE); pev->nextthink = gpGlobals->time + 0.1; pev->frame = RANDOM_FLOAT(0, 255); m_pGlow = CSprite::SpriteCreate(XEN_PLANT_GLOW_SPRITE, pev->origin + Vector(0, 0, (pev->mins.z + pev->maxs.z) * 0.5), false); m_pGlow->SetTransparency(kRenderGlow, pev->rendercolor.x, pev->rendercolor.y, pev->rendercolor.z, pev->renderamt, pev->renderfx); m_pGlow->SetAttachment(edict(), 1); } void CXenPLight::Precache() { PRECACHE_MODEL("models/light.mdl"); PRECACHE_MODEL(XEN_PLANT_GLOW_SPRITE); } void CXenPLight::Think() { StudioFrameAdvance(); pev->nextthink = gpGlobals->time + 0.1; switch (GetActivity()) { case ACT_CROUCH: if (m_fSequenceFinished) { SetActivity(ACT_CROUCHIDLE); LightOff(); } break; case ACT_CROUCHIDLE: if (gpGlobals->time > pev->dmgtime) { SetActivity(ACT_STAND); LightOn(); } break; case ACT_STAND: if (m_fSequenceFinished) SetActivity(ACT_IDLE); break; case ACT_IDLE: default: break; } } void CXenPLight::Touch(CBaseEntity* pOther) { if (pOther->IsPlayer()) { pev->dmgtime = gpGlobals->time + XEN_PLANT_HIDE_TIME; if (GetActivity() == ACT_IDLE || GetActivity() == ACT_STAND) { SetActivity(ACT_CROUCH); } } } void CXenPLight::LightOn() { SUB_UseTargets(this, USE_ON, 0); if (m_pGlow) m_pGlow->pev->effects &= ~EF_NODRAW; } void CXenPLight::LightOff() { SUB_UseTargets(this, USE_OFF, 0); if (m_pGlow) m_pGlow->pev->effects |= EF_NODRAW; } class CXenHair : public CActAnimating { public: void Spawn() override; void Precache() override; void Think() override; }; LINK_ENTITY_TO_CLASS(xen_hair, CXenHair); #define SF_HAIR_SYNC 0x0001 void CXenHair::Spawn() { Precache(); SET_MODEL(edict(), "models/hair.mdl"); UTIL_SetSize(pev, Vector(-4, -4, 0), Vector(4, 4, 32)); pev->sequence = 0; if ((pev->spawnflags & SF_HAIR_SYNC) == 0) { pev->frame = RANDOM_FLOAT(0, 255); pev->framerate = RANDOM_FLOAT(0.7, 1.4); } ResetSequenceInfo(); pev->solid = SOLID_NOT; pev->movetype = MOVETYPE_NONE; pev->nextthink = gpGlobals->time + RANDOM_FLOAT(0.1, 0.4); // Load balance these a bit } void CXenHair::Think() { StudioFrameAdvance(); pev->nextthink = gpGlobals->time + 0.5; } void CXenHair::Precache() { PRECACHE_MODEL("models/hair.mdl"); } class CXenTreeTrigger : public CBaseEntity { public: void Touch(CBaseEntity* pOther) override; static CXenTreeTrigger* TriggerCreate(edict_t* pOwner, const Vector& position); }; LINK_ENTITY_TO_CLASS(xen_ttrigger, CXenTreeTrigger); CXenTreeTrigger* CXenTreeTrigger::TriggerCreate(edict_t* pOwner, const Vector& position) { CXenTreeTrigger* pTrigger = GetClassPtr((CXenTreeTrigger*)NULL); pTrigger->pev->origin = position; pTrigger->pev->classname = MAKE_STRING("xen_ttrigger"); pTrigger->pev->solid = SOLID_TRIGGER; pTrigger->pev->movetype = MOVETYPE_NONE; pTrigger->pev->owner = pOwner; return pTrigger; } void CXenTreeTrigger::Touch(CBaseEntity* pOther) { if (pev->owner) { CBaseEntity* pEntity = CBaseEntity::Instance(pev->owner); pEntity->Touch(pOther); } } #define TREE_AE_ATTACK 1 class CXenTree : public CActAnimating { public: void Spawn() override; void Precache() override; void Touch(CBaseEntity* pOther) override; void Think() override; bool TakeDamage(entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType) override { Attack(); return false; } void HandleAnimEvent(MonsterEvent_t* pEvent) override; void Attack(); int Classify() override { return CLASS_BARNACLE; } bool Save(CSave& save) override; bool Restore(CRestore& restore) override; static TYPEDESCRIPTION m_SaveData[]; static const char* pAttackHitSounds[]; static const char* pAttackMissSounds[]; private: CXenTreeTrigger* m_pTrigger; }; LINK_ENTITY_TO_CLASS(xen_tree, CXenTree); TYPEDESCRIPTION CXenTree::m_SaveData[] = { DEFINE_FIELD(CXenTree, m_pTrigger, FIELD_CLASSPTR), }; IMPLEMENT_SAVERESTORE(CXenTree, CActAnimating); void CXenTree::Spawn() { Precache(); SET_MODEL(ENT(pev), "models/tree.mdl"); pev->movetype = MOVETYPE_NONE; pev->solid = SOLID_BBOX; pev->takedamage = DAMAGE_YES; UTIL_SetSize(pev, Vector(-30, -30, 0), Vector(30, 30, 188)); SetActivity(ACT_IDLE); pev->nextthink = gpGlobals->time + 0.1; pev->frame = RANDOM_FLOAT(0, 255); pev->framerate = RANDOM_FLOAT(0.7, 1.4); Vector triggerPosition; UTIL_MakeVectorsPrivate(pev->angles, triggerPosition, NULL, NULL); triggerPosition = pev->origin + (triggerPosition * 64); // Create the trigger m_pTrigger = CXenTreeTrigger::TriggerCreate(edict(), triggerPosition); UTIL_SetSize(m_pTrigger->pev, Vector(-24, -24, 0), Vector(24, 24, 128)); } const char* CXenTree::pAttackHitSounds[] = { "zombie/claw_strike1.wav", "zombie/claw_strike2.wav", "zombie/claw_strike3.wav", }; const char* CXenTree::pAttackMissSounds[] = { "zombie/claw_miss1.wav", "zombie/claw_miss2.wav", }; void CXenTree::Precache() { PRECACHE_MODEL("models/tree.mdl"); PRECACHE_MODEL(XEN_PLANT_GLOW_SPRITE); PRECACHE_SOUND_ARRAY(pAttackHitSounds); PRECACHE_SOUND_ARRAY(pAttackMissSounds); } void CXenTree::Touch(CBaseEntity* pOther) { if (!pOther->IsPlayer() && FClassnameIs(pOther->pev, "monster_bigmomma")) return; Attack(); } void CXenTree::Attack() { if (GetActivity() == ACT_IDLE) { SetActivity(ACT_MELEE_ATTACK1); pev->framerate = RANDOM_FLOAT(1.0, 1.4); EMIT_SOUND_ARRAY_DYN(CHAN_WEAPON, pAttackMissSounds); } } void CXenTree::HandleAnimEvent(MonsterEvent_t* pEvent) { switch (pEvent->event) { case TREE_AE_ATTACK: { CBaseEntity* pList[8]; bool sound = false; int count = UTIL_EntitiesInBox(pList, 8, m_pTrigger->pev->absmin, m_pTrigger->pev->absmax, FL_MONSTER | FL_CLIENT); Vector forward; UTIL_MakeVectorsPrivate(pev->angles, forward, NULL, NULL); for (int i = 0; i < count; i++) { if (pList[i] != this) { if (pList[i]->pev->owner != edict()) { sound = true; pList[i]->TakeDamage(pev, pev, 25, DMG_CRUSH | DMG_SLASH); pList[i]->pev->punchangle.x = 15; pList[i]->pev->velocity = pList[i]->pev->velocity + forward * 100; } } } if (sound) { EMIT_SOUND_ARRAY_DYN(CHAN_WEAPON, pAttackHitSounds); } } return; } CActAnimating::HandleAnimEvent(pEvent); } void CXenTree::Think() { float flInterval = StudioFrameAdvance(); pev->nextthink = gpGlobals->time + 0.1; DispatchAnimEvents(flInterval); switch (GetActivity()) { case ACT_MELEE_ATTACK1: if (m_fSequenceFinished) { SetActivity(ACT_IDLE); pev->framerate = RANDOM_FLOAT(0.6, 1.4); } break; default: case ACT_IDLE: break; } } // UNDONE: These need to smoke somehow when they take damage // Touch behavior? // Cause damage in smoke area // // Spores // class CXenSpore : public CActAnimating { public: void Spawn() override; void Precache() override; void Touch(CBaseEntity* pOther) override; void Think() override; bool TakeDamage(entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType) override { Attack(); return false; } // void HandleAnimEvent( MonsterEvent_t *pEvent ); void Attack() {} static const char* pModelNames[]; }; class CXenSporeSmall : public CXenSpore { void Spawn() override; }; class CXenSporeMed : public CXenSpore { void Spawn() override; }; class CXenSporeLarge : public CXenSpore { void Spawn() override; static const Vector m_hullSizes[]; }; // Fake collision box for big spores class CXenHull : public CPointEntity { public: static CXenHull* CreateHull(CBaseEntity* source, const Vector& mins, const Vector& maxs, const Vector& offset); int Classify() override { return CLASS_BARNACLE; } }; CXenHull* CXenHull::CreateHull(CBaseEntity* source, const Vector& mins, const Vector& maxs, const Vector& offset) { CXenHull* pHull = GetClassPtr((CXenHull*)NULL); UTIL_SetOrigin(pHull->pev, source->pev->origin + offset); SET_MODEL(pHull->edict(), STRING(source->pev->model)); pHull->pev->solid = SOLID_BBOX; pHull->pev->classname = MAKE_STRING("xen_hull"); pHull->pev->movetype = MOVETYPE_NONE; pHull->pev->owner = source->edict(); UTIL_SetSize(pHull->pev, mins, maxs); pHull->pev->renderamt = 0; pHull->pev->rendermode = kRenderTransTexture; // pHull->pev->effects = EF_NODRAW; return pHull; } LINK_ENTITY_TO_CLASS(xen_spore_small, CXenSporeSmall); LINK_ENTITY_TO_CLASS(xen_spore_medium, CXenSporeMed); LINK_ENTITY_TO_CLASS(xen_spore_large, CXenSporeLarge); LINK_ENTITY_TO_CLASS(xen_hull, CXenHull); void CXenSporeSmall::Spawn() { pev->skin = 0; CXenSpore::Spawn(); UTIL_SetSize(pev, Vector(-16, -16, 0), Vector(16, 16, 64)); } void CXenSporeMed::Spawn() { pev->skin = 1; CXenSpore::Spawn(); UTIL_SetSize(pev, Vector(-40, -40, 0), Vector(40, 40, 120)); } // I just eyeballed these -- fill in hulls for the legs const Vector CXenSporeLarge::m_hullSizes[] = { Vector(90, -25, 0), Vector(25, 75, 0), Vector(-15, -100, 0), Vector(-90, -35, 0), Vector(-90, 60, 0), }; void CXenSporeLarge::Spawn() { pev->skin = 2; CXenSpore::Spawn(); UTIL_SetSize(pev, Vector(-48, -48, 110), Vector(48, 48, 240)); Vector forward, right; UTIL_MakeVectorsPrivate(pev->angles, forward, right, NULL); // Rotate the leg hulls into position for (int i = 0; i < ARRAYSIZE(m_hullSizes); i++) CXenHull::CreateHull(this, Vector(-12, -12, 0), Vector(12, 12, 120), (m_hullSizes[i].x * forward) + (m_hullSizes[i].y * right)); } void CXenSpore::Spawn() { Precache(); SET_MODEL(ENT(pev), pModelNames[pev->skin]); pev->movetype = MOVETYPE_NONE; pev->solid = SOLID_BBOX; pev->takedamage = DAMAGE_YES; // SetActivity( ACT_IDLE ); pev->sequence = 0; pev->frame = RANDOM_FLOAT(0, 255); pev->framerate = RANDOM_FLOAT(0.7, 1.4); ResetSequenceInfo(); pev->nextthink = gpGlobals->time + RANDOM_FLOAT(0.1, 0.4); // Load balance these a bit } const char* CXenSpore::pModelNames[] = { "models/fungus(small).mdl", "models/fungus.mdl", "models/fungus(large).mdl", }; void CXenSpore::Precache() { PRECACHE_MODEL((char*)pModelNames[pev->skin]); } void CXenSpore::Touch(CBaseEntity* pOther) { } void CXenSpore::Think() { float flInterval = StudioFrameAdvance(); pev->nextthink = gpGlobals->time + 0.1; #if 0 DispatchAnimEvents( flInterval ); switch( GetActivity() ) { default: case ACT_IDLE: break; } #endif }