#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules #include "IncludeAllMonsters.h" #include "../Root.h" #include "../Server.h" #include "../ClientHandle.h" #include "../World.h" #include "../Entities/Player.h" #include "../Entities/ExpOrb.h" #include "../Defines.h" #include "../MonsterConfig.h" #include "../MersenneTwister.h" #include "../Vector3f.h" #include "../Vector3i.h" #include "../Vector3d.h" #include "../Tracer.h" #include "../Chunk.h" #include "../FastRandom.h" /** Map for eType <-> string Needs to be alpha-sorted by the strings, because binary search is used in StringToMobType() The strings need to be lowercase (for more efficient comparisons in StringToMobType()) */ static const struct { cMonster::eType m_Type; const char * m_lcName; } g_MobTypeNames[] = { {cMonster::mtBat, "bat"}, {cMonster::mtBlaze, "blaze"}, {cMonster::mtCaveSpider, "cavespider"}, {cMonster::mtChicken, "chicken"}, {cMonster::mtCow, "cow"}, {cMonster::mtCreeper, "creeper"}, {cMonster::mtEnderman, "enderman"}, {cMonster::mtEnderDragon, "enderdragon"}, {cMonster::mtGhast, "ghast"}, {cMonster::mtHorse, "horse"}, {cMonster::mtIronGolem, "irongolem"}, {cMonster::mtMagmaCube, "magmacube"}, {cMonster::mtMooshroom, "mooshroom"}, {cMonster::mtOcelot, "ocelot"}, {cMonster::mtPig, "pig"}, {cMonster::mtSheep, "sheep"}, {cMonster::mtSilverfish, "silverfish"}, {cMonster::mtSkeleton, "skeleton"}, {cMonster::mtSnowGolem, "snowgolem"}, {cMonster::mtSlime, "slime"}, {cMonster::mtSpider, "spider"}, {cMonster::mtSquid, "squid"}, {cMonster::mtVillager, "villager"}, {cMonster::mtWitch, "witch"}, {cMonster::mtWither, "wither"}, {cMonster::mtWolf, "wolf"}, {cMonster::mtZombie, "zombie"}, {cMonster::mtZombiePigman, "zombiepigman"}, } ; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // cMonster: cMonster::cMonster(const AString & a_ConfigName, eType a_MobType, const AString & a_SoundHurt, const AString & a_SoundDeath, double a_Width, double a_Height) : super(etMonster, a_Width, a_Height) , m_EMState(IDLE) , m_EMPersonality(AGGRESSIVE) , m_SightDistance(25) , m_Target(NULL) , m_AttackRate(3) , m_IdleInterval(0) , m_bMovingToDestination(false) , m_DestinationTime( 0 ) , m_DestroyTimer( 0 ) , m_Jump(0) , m_MobType(a_MobType) , m_SoundHurt(a_SoundHurt) , m_SoundDeath(a_SoundDeath) , m_SeePlayerInterval (0) , m_AttackDamage(1.0f) , m_AttackRange(2.0f) , m_AttackInterval(0) , m_BurnsInDaylight(false) { if (!a_ConfigName.empty()) { GetMonsterConfig(a_ConfigName); } } void cMonster::SpawnOn(cClientHandle & a_Client) { a_Client.SendSpawnMob(*this); } void cMonster::MoveToPosition( const Vector3f & a_Position ) { m_bMovingToDestination = true; m_Destination = a_Position; } bool cMonster::ReachedDestination() { Vector3f Distance = (m_Destination) - GetPosition(); if( Distance.SqrLength() < 2.f ) return true; return false; } void cMonster::Tick(float a_Dt, cChunk & a_Chunk) { super::Tick(a_Dt, a_Chunk); if (m_Health <= 0) { // The mob is dead, but we're still animating the "puff" they leave when they die m_DestroyTimer += a_Dt / 1000; if (m_DestroyTimer > 1) { Destroy(true); } return; } // Burning in daylight HandleDaylightBurning(a_Chunk); HandlePhysics(a_Dt,a_Chunk); BroadcastMovementUpdate(); a_Dt /= 1000; if (m_bMovingToDestination) { Vector3f Pos( GetPosition() ); Vector3f Distance = m_Destination - Pos; if( !ReachedDestination() ) { Distance.y = 0; Distance.Normalize(); Distance *= 3; SetSpeedX( Distance.x ); SetSpeedZ( Distance.z ); if (m_EMState == ESCAPING) { //Runs Faster when escaping :D otherwise they just walk away SetSpeedX (GetSpeedX() * 2.f); SetSpeedZ (GetSpeedZ() * 2.f); } } else { m_bMovingToDestination = false; } if( GetSpeed().SqrLength() > 0.f ) { if( m_bOnGround ) { Vector3f NormSpeed = Vector3f(GetSpeed()).NormalizeCopy(); Vector3f NextBlock = Vector3f( GetPosition() ) + NormSpeed; int NextHeight; if (!m_World->TryGetHeight((int)NextBlock.x, (int)NextBlock.z, NextHeight)) { // The chunk at NextBlock is not loaded return; } if( NextHeight > (GetPosY() - 1.0) && (NextHeight - GetPosY()) < 2.5 ) { m_bOnGround = false; SetSpeedY(5.f); // Jump!! } } } } Vector3d Distance = m_Destination - GetPosition(); if (Distance.SqrLength() > 0.1f) { double Rotation, Pitch; Distance.Normalize(); VectorToEuler( Distance.x, Distance.y, Distance.z, Rotation, Pitch ); SetHeadYaw (Rotation); SetYaw( Rotation ); SetPitch( -Pitch ); } switch (m_EMState) { case IDLE: { // If enemy passive we ignore checks for player visibility InStateIdle(a_Dt); break; } case CHASING: { // If we do not see a player anymore skip chasing action InStateChasing(a_Dt); break; } case ESCAPING: { InStateEscaping(a_Dt); break; } } // switch (m_EMState) } void cMonster::DoTakeDamage(TakeDamageInfo & a_TDI) { super::DoTakeDamage(a_TDI); if((m_SoundHurt != "") && (m_Health > 0)) m_World->BroadcastSoundEffect(m_SoundHurt, (int)(GetPosX() * 8), (int)(GetPosY() * 8), (int)(GetPosZ() * 8), 1.0f, 0.8f); if (a_TDI.Attacker != NULL) { m_Target = a_TDI.Attacker; AddReference(m_Target); } } void cMonster::KilledBy(cEntity * a_Killer) { super::KilledBy(a_Killer); if (m_SoundHurt != "") { m_World->BroadcastSoundEffect(m_SoundDeath, (int)(GetPosX() * 8), (int)(GetPosY() * 8), (int)(GetPosZ() * 8), 1.0f, 0.8f); } int Reward; switch (m_MobType) { // Animals case cMonster::mtChicken: case cMonster::mtCow: case cMonster::mtHorse: case cMonster::mtPig: case cMonster::mtSheep: case cMonster::mtSquid: case cMonster::mtMooshroom: case cMonster::mtOcelot: case cMonster::mtWolf: { Reward = m_World->GetTickRandomNumber(2) + 1; break; } // Monsters case cMonster::mtCaveSpider: case cMonster::mtCreeper: case cMonster::mtEnderman: case cMonster::mtGhast: case cMonster::mtSilverfish: case cMonster::mtSkeleton: case cMonster::mtSpider: case cMonster::mtWitch: case cMonster::mtZombie: case cMonster::mtZombiePigman: case cMonster::mtSlime: case cMonster::mtMagmaCube: { Reward = 6 + (m_World->GetTickRandomNumber(2)); break; } case cMonster::mtBlaze: { Reward = 10; break; } // Bosses case cMonster::mtEnderDragon: { Reward = 12000; break; } case cMonster::mtWither: { Reward = 50; break; } default: { Reward = 0; break; } } m_World->SpawnExperienceOrb(GetPosX(), GetPosY(), GetPosZ(), Reward); m_DestroyTimer = 0; } //----State Logic const char *cMonster::GetState() { switch(m_EMState) { case IDLE: return "Idle"; case ATTACKING: return "Attacking"; case CHASING: return "Chasing"; default: return "Unknown"; } } // for debugging void cMonster::SetState(const AString & a_State) { if (a_State.compare("Idle") == 0) { m_EMState = IDLE; } else if (a_State.compare("Attacking") == 0) { m_EMState = ATTACKING; } else if (a_State.compare("Chasing") == 0) { m_EMState = CHASING; } else { LOGD("cMonster::SetState(): Invalid state"); ASSERT(!"Invalid state"); } } //Checks to see if EventSeePlayer should be fired //monster sez: Do I see the player void cMonster::CheckEventSeePlayer(void) { // TODO: Rewrite this to use cWorld's DoWithPlayers() cPlayer * Closest = FindClosestPlayer(); if (Closest != NULL) { EventSeePlayer(Closest); } } void cMonster::CheckEventLostPlayer(void) { Vector3f pos; cTracer LineOfSight(GetWorld()); if (m_Target != NULL) { pos = m_Target->GetPosition(); if ((pos - GetPosition()).Length() > m_SightDistance || LineOfSight.Trace(GetPosition(),(pos - GetPosition()), (int)(pos - GetPosition()).Length())) { EventLosePlayer(); } } else { EventLosePlayer(); } } // What to do if player is seen // default to change state to chasing void cMonster::EventSeePlayer(cEntity * a_SeenPlayer) { m_Target = a_SeenPlayer; AddReference(m_Target); } void cMonster::EventLosePlayer(void) { Dereference(m_Target); m_Target = NULL; m_EMState = IDLE; } // What to do if in Idle State void cMonster::InStateIdle(float a_Dt) { m_IdleInterval += a_Dt; if (m_IdleInterval > 1) { // at this interval the results are predictable int rem = m_World->GetTickRandomNumber(6) + 1; // LOGD("Moving: int: %3.3f rem: %i",idle_interval,rem); m_IdleInterval -= 1; // So nothing gets dropped when the server hangs for a few seconds Vector3f Dist; Dist.x = (float)(m_World->GetTickRandomNumber(10) - 5); Dist.z = (float)(m_World->GetTickRandomNumber(10) - 5); if ((Dist.SqrLength() > 2) && (rem >= 3)) { m_Destination.x = (float)(GetPosX() + Dist.x); m_Destination.z = (float)(GetPosZ() + Dist.z); int PosY; if (m_World->TryGetHeight((int)m_Destination.x, (int)m_Destination.z, PosY)) { m_Destination.y = (float)PosY + 1.2f; MoveToPosition(m_Destination); } } } } // What to do if in Chasing State // This state should always be defined in each child class void cMonster::InStateChasing(float a_Dt) { UNUSED(a_Dt); } // What to do if in Escaping State void cMonster::InStateEscaping(float a_Dt) { UNUSED(a_Dt); if (m_Target != NULL) { Vector3d newloc = GetPosition(); newloc.x = (m_Target->GetPosition().x < newloc.x)? (newloc.x + m_SightDistance): (newloc.x - m_SightDistance); newloc.z = (m_Target->GetPosition().z < newloc.z)? (newloc.z + m_SightDistance): (newloc.z - m_SightDistance); MoveToPosition(newloc); } else { m_EMState = IDLE; // This shouldnt be required but just to be safe } } // Do attack here // a_Dt is passed so we can set attack rate void cMonster::Attack(float a_Dt) { m_AttackInterval += a_Dt * m_AttackRate; if ((m_Target != NULL) && (m_AttackInterval > 3.0)) { // Setting this higher gives us more wiggle room for attackrate m_AttackInterval = 0.0; ((cPawn *)m_Target)->TakeDamage(*this); } } // Checks for Players close by and if they are visible return the closest cPlayer * cMonster::FindClosestPlayer(void) { return m_World->FindClosestPlayer(GetPosition(), m_SightDistance); } void cMonster::GetMonsterConfig(const AString & a_Name) { cRoot::Get()->GetMonsterConfig()->AssignAttributes(this, a_Name); } void cMonster::SetAttackRate(int ar) { m_AttackRate = (float)ar; } void cMonster::SetAttackRange(float ar) { m_AttackRange = ar; } void cMonster::SetAttackDamage(float ad) { m_AttackDamage = ad; } void cMonster::SetSightDistance(float sd) { m_SightDistance = sd; } AString cMonster::MobTypeToString(cMonster::eType a_MobType) { // Mob types aren't sorted, so we need to search linearly: for (size_t i = 0; i < ARRAYCOUNT(g_MobTypeNames); i++) { if (g_MobTypeNames[i].m_Type == a_MobType) { return g_MobTypeNames[i].m_lcName; } } // Not found: return ""; } cMonster::eType cMonster::StringToMobType(const AString & a_Name) { AString lcName(a_Name); StrToLower(lcName); // Binary-search for the lowercase name: int lo = 0, hi = ARRAYCOUNT(g_MobTypeNames) - 1; while (hi - lo > 1) { int mid = (lo + hi) / 2; int res = strcmp(g_MobTypeNames[mid].m_lcName, lcName.c_str()); if (res == 0) { return g_MobTypeNames[mid].m_Type; } if (res < 0) { lo = mid; } else { hi = mid; } } // Range has collapsed to at most two elements, compare each: if (strcmp(g_MobTypeNames[lo].m_lcName, lcName.c_str()) == 0) { return g_MobTypeNames[lo].m_Type; } if ((lo != hi) && (strcmp(g_MobTypeNames[hi].m_lcName, lcName.c_str()) == 0)) { return g_MobTypeNames[hi].m_Type; } // Not found: return mtInvalidType; } cMonster::eFamily cMonster::FamilyFromType(eType a_Type) { switch (a_Type) { case mtBat: return mfAmbient; case mtBlaze: return mfHostile; case mtCaveSpider: return mfHostile; case mtChicken: return mfPassive; case mtCow: return mfPassive; case mtCreeper: return mfHostile; case mtEnderman: return mfHostile; case mtGhast: return mfHostile; case mtHorse: return mfPassive; case mtIronGolem: return mfPassive; case mtMagmaCube: return mfHostile; case mtMooshroom: return mfHostile; case mtOcelot: return mfPassive; case mtPig: return mfPassive; case mtSheep: return mfPassive; case mtSilverfish: return mfHostile; case mtSkeleton: return mfHostile; case mtSlime: return mfHostile; case mtSpider: return mfHostile; case mtSquid: return mfWater; case mtVillager: return mfPassive; case mtWitch: return mfHostile; case mtWolf: return mfHostile; case mtZombie: return mfHostile; case mtZombiePigman: return mfHostile; } ; ASSERT(!"Unhandled mob type"); return mfMaxplusone; } int cMonster::GetSpawnDelay(cMonster::eFamily a_MobFamily) { switch (a_MobFamily) { case mfHostile: return 40; case mfPassive: return 40; case mfAmbient: return 40; case mfWater: return 400; } ASSERT(!"Unhandled mob family"); return -1; } cMonster * cMonster::NewMonsterFromType(cMonster::eType a_MobType) { cFastRandom Random; cMonster * toReturn = NULL; // Create the mob entity switch (a_MobType) { case mtMagmaCube: case mtSlime: { toReturn = new cSlime (Random.NextInt(2) + 1); break; } case mtSkeleton: { // TODO: Actual detection of spawning in Nether toReturn = new cSkeleton(Random.NextInt(1) == 0 ? false : true); break; } case mtVillager: { int VillagerType = Random.NextInt(6); if (VillagerType == 6) { // Give farmers a better chance of spawning VillagerType = 0; } toReturn = new cVillager((cVillager::eVillagerType)VillagerType); break; } case mtHorse: { // Horses take a type (species), a colour, and a style (dots, stripes, etc.) int HorseType = Random.NextInt(7); int HorseColor = Random.NextInt(6); int HorseStyle = Random.NextInt(6); int HorseTameTimes = Random.NextInt(6) + 1; if ((HorseType == 5) || (HorseType == 6) || (HorseType == 7)) { // Increase chances of normal horse (zero) HorseType = 0; } toReturn = new cHorse(HorseType, HorseColor, HorseStyle, HorseTameTimes); break; } case mtBat: toReturn = new cBat(); break; case mtBlaze: toReturn = new cBlaze(); break; case mtCaveSpider: toReturn = new cCavespider(); break; case mtChicken: toReturn = new cChicken(); break; case mtCow: toReturn = new cCow(); break; case mtCreeper: toReturn = new cCreeper(); break; case mtEnderDragon: toReturn = new cEnderDragon(); break; case mtEnderman: toReturn = new cEnderman(); break; case mtGhast: toReturn = new cGhast(); break; case mtIronGolem: toReturn = new cIronGolem(); break; case mtMooshroom: toReturn = new cMooshroom(); break; case mtOcelot: toReturn = new cOcelot(); break; case mtPig: toReturn = new cPig(); break; case mtSheep: toReturn = new cSheep (Random.NextInt(15)); break; // Colour parameter case mtSilverfish: toReturn = new cSilverfish(); break; case mtSnowGolem: toReturn = new cSnowGolem(); break; case mtSpider: toReturn = new cSpider(); break; case mtSquid: toReturn = new cSquid(); break; case mtWitch: toReturn = new cWitch(); break; case mtWither: toReturn = new cWither(); break; case mtWolf: toReturn = new cWolf(); break; case mtZombie: toReturn = new cZombie(false); break; // TODO: Infected zombie parameter case mtZombiePigman: toReturn = new cZombiePigman(); break; default: { ASSERT(!"Unhandled mob type whilst trying to spawn mob!"); } } return toReturn; } void cMonster::AddRandomDropItem(cItems & a_Drops, unsigned int a_Min, unsigned int a_Max, short a_Item, short a_ItemHealth) { MTRand r1; int Count = r1.randInt() % (a_Max + 1 - a_Min) + a_Min; if (Count > 0) { a_Drops.push_back(cItem(a_Item, Count, a_ItemHealth)); } } void cMonster::HandleDaylightBurning(cChunk & a_Chunk) { if (!m_BurnsInDaylight) { return; } int RelY = (int)floor(GetPosY()); if ((RelY < 0) || (RelY >= cChunkDef::Height)) { // Outside the world return; } int RelX = (int)floor(GetPosX()) - GetChunkX() * cChunkDef::Width; int RelZ = (int)floor(GetPosZ()) - GetChunkZ() * cChunkDef::Width; if ( (a_Chunk.GetSkyLight(RelX, RelY, RelZ) == 15) && // In the daylight (a_Chunk.GetBlock(RelX, RelY, RelZ) != E_BLOCK_SOULSAND) && // Not on soulsand (GetWorld()->GetTimeOfDay() < (12000 + 1000)) && // It is nighttime !IsOnFire() // Not already burning ) { // Burn for 100 ticks, then decide again StartBurning(100); } } cMonster::eFamily cMonster::GetMobFamily(void) const { return FamilyFromType(m_MobType); }