All mobs now drown (fixes #54)
* Implemented mob drowning * Iron Golems and squids are excluded
This commit is contained in:
parent
1112f5adc6
commit
fd7fc7e59e
|
@ -57,6 +57,8 @@ cEntity::cEntity(eEntityType a_EntityType, double a_X, double a_Y, double a_Z, d
|
||||||
, m_Mass (0.001) // Default 1g
|
, m_Mass (0.001) // Default 1g
|
||||||
, m_Width(a_Width)
|
, m_Width(a_Width)
|
||||||
, m_Height(a_Height)
|
, m_Height(a_Height)
|
||||||
|
, m_IsSubmerged(false)
|
||||||
|
, m_IsSwimming(false)
|
||||||
{
|
{
|
||||||
cCSLock Lock(m_CSCount);
|
cCSLock Lock(m_CSCount);
|
||||||
m_EntityCount++;
|
m_EntityCount++;
|
||||||
|
@ -529,7 +531,17 @@ void cEntity::Tick(float a_Dt, cChunk & a_Chunk)
|
||||||
{
|
{
|
||||||
TickInVoid(a_Chunk);
|
TickInVoid(a_Chunk);
|
||||||
}
|
}
|
||||||
else { m_TicksSinceLastVoidDamage = 0; }
|
else
|
||||||
|
m_TicksSinceLastVoidDamage = 0;
|
||||||
|
|
||||||
|
if (IsMob() || IsPlayer())
|
||||||
|
{
|
||||||
|
// Set swimming state
|
||||||
|
SetSwimState(a_Chunk);
|
||||||
|
|
||||||
|
// Handle drowning
|
||||||
|
HandleAir();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -907,6 +919,87 @@ void cEntity::TickInVoid(cChunk & a_Chunk)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cEntity::SetSwimState(cChunk & a_Chunk)
|
||||||
|
{
|
||||||
|
int RelY = (int)floor(m_LastPosY + 0.1);
|
||||||
|
if ((RelY < 0) || (RelY >= cChunkDef::Height - 1))
|
||||||
|
{
|
||||||
|
m_IsSwimming = false;
|
||||||
|
m_IsSubmerged = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BLOCKTYPE BlockIn;
|
||||||
|
int RelX = (int)floor(m_LastPosX) - a_Chunk.GetPosX() * cChunkDef::Width;
|
||||||
|
int RelZ = (int)floor(m_LastPosZ) - a_Chunk.GetPosZ() * cChunkDef::Width;
|
||||||
|
|
||||||
|
// Check if the player is swimming:
|
||||||
|
// Use Unbounded, because we're being called *after* processing super::Tick(), which could have changed our chunk
|
||||||
|
if (!a_Chunk.UnboundedRelGetBlockType(RelX, RelY, RelZ, BlockIn))
|
||||||
|
{
|
||||||
|
// This sometimes happens on Linux machines
|
||||||
|
// Ref.: http://forum.mc-server.org/showthread.php?tid=1244
|
||||||
|
LOGD("SetSwimState failure: RelX = %d, RelZ = %d, LastPos = {%.02f, %.02f}, Pos = %.02f, %.02f}",
|
||||||
|
RelX, RelY, m_LastPosX, m_LastPosZ, GetPosX(), GetPosZ()
|
||||||
|
);
|
||||||
|
m_IsSwimming = false;
|
||||||
|
m_IsSubmerged = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_IsSwimming = IsBlockWater(BlockIn);
|
||||||
|
|
||||||
|
// Check if the player is submerged:
|
||||||
|
VERIFY(a_Chunk.UnboundedRelGetBlockType(RelX, RelY + 1, RelZ, BlockIn));
|
||||||
|
m_IsSubmerged = IsBlockWater(BlockIn);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cEntity::HandleAir(void)
|
||||||
|
{
|
||||||
|
// Ref.: http://www.minecraftwiki.net/wiki/Chunk_format
|
||||||
|
// See if the entity is /submerged/ water (block above is water)
|
||||||
|
// Get the type of block the entity is standing in:
|
||||||
|
|
||||||
|
if (IsSubmerged())
|
||||||
|
{
|
||||||
|
SetSpeedY(1); // Float in the water
|
||||||
|
|
||||||
|
// Either reduce air level or damage player
|
||||||
|
if (m_AirLevel < 1)
|
||||||
|
{
|
||||||
|
if (m_AirTickTimer < 1)
|
||||||
|
{
|
||||||
|
// Damage player
|
||||||
|
TakeDamage(dtDrowning, NULL, 1, 1, 0);
|
||||||
|
// Reset timer
|
||||||
|
m_AirTickTimer = DROWNING_TICKS;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_AirTickTimer -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Reduce air supply
|
||||||
|
m_AirLevel -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Set the air back to maximum
|
||||||
|
m_AirLevel = MAX_AIR_LEVEL;
|
||||||
|
m_AirTickTimer = DROWNING_TICKS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// Called when the entity starts burning
|
/// Called when the entity starts burning
|
||||||
void cEntity::OnStartedBurning(void)
|
void cEntity::OnStartedBurning(void)
|
||||||
{
|
{
|
||||||
|
|
|
@ -110,6 +110,8 @@ public:
|
||||||
BURN_TICKS_PER_DAMAGE = 20, ///< How many ticks to wait between damaging an entity when it is burning
|
BURN_TICKS_PER_DAMAGE = 20, ///< How many ticks to wait between damaging an entity when it is burning
|
||||||
BURN_DAMAGE = 1, ///< How much damage to deal when the entity is burning
|
BURN_DAMAGE = 1, ///< How much damage to deal when the entity is burning
|
||||||
BURN_TICKS = 200, ///< How long to keep an entity burning after it has stood in lava / fire
|
BURN_TICKS = 200, ///< How long to keep an entity burning after it has stood in lava / fire
|
||||||
|
MAX_AIR_LEVEL = 300, ///< Maximum air an entity can have
|
||||||
|
DROWNING_TICKS = 20, ///< Number of ticks per heart of damage
|
||||||
} ;
|
} ;
|
||||||
|
|
||||||
cEntity(eEntityType a_EntityType, double a_X, double a_Y, double a_Z, double a_Width, double a_Height);
|
cEntity(eEntityType a_EntityType, double a_X, double a_Y, double a_Z, double a_Width, double a_Height);
|
||||||
|
@ -344,7 +346,14 @@ public:
|
||||||
virtual bool IsRiding (void) const {return false; }
|
virtual bool IsRiding (void) const {return false; }
|
||||||
virtual bool IsSprinting(void) const {return false; }
|
virtual bool IsSprinting(void) const {return false; }
|
||||||
virtual bool IsRclking (void) const {return false; }
|
virtual bool IsRclking (void) const {return false; }
|
||||||
virtual bool IsInvisible(void) const {return false; }
|
virtual bool IsInvisible(void) const { return false; }
|
||||||
|
|
||||||
|
/** Returns whether the player is swimming or not */
|
||||||
|
virtual bool IsSwimming(void) const{ return m_IsSwimming; }
|
||||||
|
/** Return whether the player is under water or not */
|
||||||
|
virtual bool IsSubmerged(void) const{ return m_IsSubmerged; }
|
||||||
|
/** Gets remaining air of a monster */
|
||||||
|
int GetAirLevel(void) const { return m_AirLevel; }
|
||||||
|
|
||||||
// tolua_end
|
// tolua_end
|
||||||
|
|
||||||
|
@ -412,6 +421,18 @@ protected:
|
||||||
virtual void Destroyed(void) {} // Called after the entity has been destroyed
|
virtual void Destroyed(void) {} // Called after the entity has been destroyed
|
||||||
|
|
||||||
void SetWorld(cWorld * a_World) { m_World = a_World; }
|
void SetWorld(cWorld * a_World) { m_World = a_World; }
|
||||||
|
|
||||||
|
/** Called in each tick to handle air-related processing i.e. drowning */
|
||||||
|
virtual void HandleAir();
|
||||||
|
/** Called once per tick to set IsSwimming and IsSubmerged */
|
||||||
|
virtual void SetSwimState(cChunk & a_Chunk);
|
||||||
|
|
||||||
|
/** If an entity is currently swimming in or submerged under water */
|
||||||
|
bool m_IsSwimming, m_IsSubmerged;
|
||||||
|
|
||||||
|
/** Air level of a mobile */
|
||||||
|
int m_AirLevel;
|
||||||
|
int m_AirTickTimer;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Measured in degrees, [-180, +180)
|
// Measured in degrees, [-180, +180)
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
#include "../UI/Window.h"
|
#include "../UI/Window.h"
|
||||||
#include "../UI/WindowOwner.h"
|
#include "../UI/WindowOwner.h"
|
||||||
#include "../World.h"
|
#include "../World.h"
|
||||||
#include "Pickup.h"
|
|
||||||
#include "../Bindings/PluginManager.h"
|
#include "../Bindings/PluginManager.h"
|
||||||
#include "../BlockEntities/BlockEntity.h"
|
#include "../BlockEntities/BlockEntity.h"
|
||||||
#include "../GroupManager.h"
|
#include "../GroupManager.h"
|
||||||
|
@ -36,8 +35,6 @@
|
||||||
|
|
||||||
cPlayer::cPlayer(cClientHandle* a_Client, const AString & a_PlayerName)
|
cPlayer::cPlayer(cClientHandle* a_Client, const AString & a_PlayerName)
|
||||||
: super(etPlayer, 0.6, 1.8)
|
: super(etPlayer, 0.6, 1.8)
|
||||||
, m_AirLevel( MAX_AIR_LEVEL )
|
|
||||||
, m_AirTickTimer(DROWNING_TICKS)
|
|
||||||
, m_bVisible(true)
|
, m_bVisible(true)
|
||||||
, m_FoodLevel(MAX_FOOD_LEVEL)
|
, m_FoodLevel(MAX_FOOD_LEVEL)
|
||||||
, m_FoodSaturationLevel(5)
|
, m_FoodSaturationLevel(5)
|
||||||
|
@ -108,9 +105,23 @@ cPlayer::cPlayer(cClientHandle* a_Client, const AString & a_PlayerName)
|
||||||
a_PlayerName.c_str(), GetPosX(), GetPosY(), GetPosZ()
|
a_PlayerName.c_str(), GetPosX(), GetPosY(), GetPosZ()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_LastJumpHeight = (float)(GetPosY());
|
m_LastJumpHeight = (float)(GetPosY());
|
||||||
m_LastGroundHeight = (float)(GetPosY());
|
m_LastGroundHeight = (float)(GetPosY());
|
||||||
m_Stance = GetPosY() + 1.62;
|
m_Stance = GetPosY() + 1.62;
|
||||||
|
|
||||||
|
if (m_GameMode == gmNotSet)
|
||||||
|
{
|
||||||
|
cWorld * World = cRoot::Get()->GetWorld(GetLoadedWorldName());
|
||||||
|
if (World == NULL)
|
||||||
|
{
|
||||||
|
World = cRoot::Get()->GetDefaultWorld();
|
||||||
|
}
|
||||||
|
if (World->IsGameModeCreative())
|
||||||
|
{
|
||||||
|
m_CanFly = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cRoot::Get()->GetServer()->PlayerCreated(this);
|
cRoot::Get()->GetServer()->PlayerCreated(this);
|
||||||
}
|
}
|
||||||
|
@ -197,12 +208,6 @@ void cPlayer::Tick(float a_Dt, cChunk & a_Chunk)
|
||||||
|
|
||||||
super::Tick(a_Dt, a_Chunk);
|
super::Tick(a_Dt, a_Chunk);
|
||||||
|
|
||||||
// Set player swimming state
|
|
||||||
SetSwimState(a_Chunk);
|
|
||||||
|
|
||||||
// Handle air drowning stuff
|
|
||||||
HandleAir();
|
|
||||||
|
|
||||||
// Handle charging the bow:
|
// Handle charging the bow:
|
||||||
if (m_IsChargingBow)
|
if (m_IsChargingBow)
|
||||||
{
|
{
|
||||||
|
@ -1630,27 +1635,12 @@ bool cPlayer::LoadFromDisk()
|
||||||
m_CurrentXp = (short) root.get("xpCurrent", 0).asInt();
|
m_CurrentXp = (short) root.get("xpCurrent", 0).asInt();
|
||||||
m_IsFlying = root.get("isflying", 0).asBool();
|
m_IsFlying = root.get("isflying", 0).asBool();
|
||||||
|
|
||||||
//SetExperience(root.get("experience", 0).asInt());
|
|
||||||
|
|
||||||
m_GameMode = (eGameMode) root.get("gamemode", eGameMode_NotSet).asInt();
|
m_GameMode = (eGameMode) root.get("gamemode", eGameMode_NotSet).asInt();
|
||||||
|
|
||||||
if (m_GameMode == eGameMode_Creative)
|
if (m_GameMode == eGameMode_Creative)
|
||||||
{
|
{
|
||||||
m_CanFly = true;
|
m_CanFly = true;
|
||||||
}
|
}
|
||||||
else if (m_GameMode == eGameMode_NotSet)
|
|
||||||
{
|
|
||||||
cWorld * World = cRoot::Get()->GetWorld(GetLoadedWorldName());
|
|
||||||
if (World == NULL)
|
|
||||||
{
|
|
||||||
World = cRoot::Get()->GetDefaultWorld();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (World->GetGameMode() == eGameMode_Creative)
|
|
||||||
{
|
|
||||||
m_CanFly = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m_Inventory.LoadFromJson(root["inventory"]);
|
m_Inventory.LoadFromJson(root["inventory"]);
|
||||||
|
|
||||||
|
@ -1767,85 +1757,6 @@ void cPlayer::UseEquippedItem(void)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void cPlayer::SetSwimState(cChunk & a_Chunk)
|
|
||||||
{
|
|
||||||
int RelY = (int)floor(m_LastPosY + 0.1);
|
|
||||||
if ((RelY < 0) || (RelY >= cChunkDef::Height - 1))
|
|
||||||
{
|
|
||||||
m_IsSwimming = false;
|
|
||||||
m_IsSubmerged = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
BLOCKTYPE BlockIn;
|
|
||||||
int RelX = (int)floor(m_LastPosX) - a_Chunk.GetPosX() * cChunkDef::Width;
|
|
||||||
int RelZ = (int)floor(m_LastPosZ) - a_Chunk.GetPosZ() * cChunkDef::Width;
|
|
||||||
|
|
||||||
// Check if the player is swimming:
|
|
||||||
// Use Unbounded, because we're being called *after* processing super::Tick(), which could have changed our chunk
|
|
||||||
if (!a_Chunk.UnboundedRelGetBlockType(RelX, RelY, RelZ, BlockIn))
|
|
||||||
{
|
|
||||||
// This sometimes happens on Linux machines
|
|
||||||
// Ref.: http://forum.mc-server.org/showthread.php?tid=1244
|
|
||||||
LOGD("SetSwimState failure: RelX = %d, RelZ = %d, LastPos = {%.02f, %.02f}, Pos = %.02f, %.02f}",
|
|
||||||
RelX, RelY, m_LastPosX, m_LastPosZ, GetPosX(), GetPosZ()
|
|
||||||
);
|
|
||||||
m_IsSwimming = false;
|
|
||||||
m_IsSubmerged = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
m_IsSwimming = IsBlockWater(BlockIn);
|
|
||||||
|
|
||||||
// Check if the player is submerged:
|
|
||||||
VERIFY(a_Chunk.UnboundedRelGetBlockType(RelX, RelY + 1, RelZ, BlockIn));
|
|
||||||
m_IsSubmerged = IsBlockWater(BlockIn);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void cPlayer::HandleAir(void)
|
|
||||||
{
|
|
||||||
// Ref.: http://www.minecraftwiki.net/wiki/Chunk_format
|
|
||||||
// see if the player is /submerged/ water (block above is water)
|
|
||||||
// Get the type of block the player's standing in:
|
|
||||||
|
|
||||||
if (IsSubmerged())
|
|
||||||
{
|
|
||||||
// either reduce air level or damage player
|
|
||||||
if (m_AirLevel < 1)
|
|
||||||
{
|
|
||||||
if (m_AirTickTimer < 1)
|
|
||||||
{
|
|
||||||
// damage player
|
|
||||||
TakeDamage(dtDrowning, NULL, 1, 1, 0);
|
|
||||||
// reset timer
|
|
||||||
m_AirTickTimer = DROWNING_TICKS;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_AirTickTimer -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// reduce air supply
|
|
||||||
m_AirLevel -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// set the air back to maximum
|
|
||||||
m_AirLevel = MAX_AIR_LEVEL;
|
|
||||||
m_AirTickTimer = DROWNING_TICKS;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void cPlayer::HandleFood(void)
|
void cPlayer::HandleFood(void)
|
||||||
{
|
{
|
||||||
// Ref.: http://www.minecraftwiki.net/wiki/Hunger
|
// Ref.: http://www.minecraftwiki.net/wiki/Hunger
|
||||||
|
|
|
@ -31,8 +31,6 @@ public:
|
||||||
MAX_HEALTH = 20,
|
MAX_HEALTH = 20,
|
||||||
MAX_FOOD_LEVEL = 20,
|
MAX_FOOD_LEVEL = 20,
|
||||||
EATING_TICKS = 30, ///< Number of ticks it takes to eat an item
|
EATING_TICKS = 30, ///< Number of ticks it takes to eat an item
|
||||||
MAX_AIR_LEVEL = 300,
|
|
||||||
DROWNING_TICKS = 10, //number of ticks per heart of damage
|
|
||||||
} ;
|
} ;
|
||||||
// tolua_end
|
// tolua_end
|
||||||
|
|
||||||
|
@ -241,8 +239,6 @@ public:
|
||||||
int GetFoodTickTimer (void) const { return m_FoodTickTimer; }
|
int GetFoodTickTimer (void) const { return m_FoodTickTimer; }
|
||||||
double GetFoodExhaustionLevel (void) const { return m_FoodExhaustionLevel; }
|
double GetFoodExhaustionLevel (void) const { return m_FoodExhaustionLevel; }
|
||||||
int GetFoodPoisonedTicksRemaining(void) const { return m_FoodPoisonedTicksRemaining; }
|
int GetFoodPoisonedTicksRemaining(void) const { return m_FoodPoisonedTicksRemaining; }
|
||||||
|
|
||||||
int GetAirLevel (void) const { return m_AirLevel; }
|
|
||||||
|
|
||||||
/// Returns true if the player is satiated, i. e. their foodlevel is at the max and they cannot eat anymore
|
/// Returns true if the player is satiated, i. e. their foodlevel is at the max and they cannot eat anymore
|
||||||
bool IsSatiated(void) const { return (m_FoodLevel >= MAX_FOOD_LEVEL); }
|
bool IsSatiated(void) const { return (m_FoodLevel >= MAX_FOOD_LEVEL); }
|
||||||
|
@ -353,12 +349,6 @@ public:
|
||||||
/// If true the player can fly even when he's not in creative.
|
/// If true the player can fly even when he's not in creative.
|
||||||
void SetCanFly(bool a_CanFly);
|
void SetCanFly(bool a_CanFly);
|
||||||
|
|
||||||
/// Returns whether the player is swimming or not
|
|
||||||
virtual bool IsSwimming(void) const{ return m_IsSwimming; }
|
|
||||||
|
|
||||||
/// Return whether the player is under water or not
|
|
||||||
virtual bool IsSubmerged(void) const{ return m_IsSubmerged; }
|
|
||||||
|
|
||||||
/// Returns wheter the player can fly or not.
|
/// Returns wheter the player can fly or not.
|
||||||
virtual bool CanFly(void) const { return m_CanFly; }
|
virtual bool CanFly(void) const { return m_CanFly; }
|
||||||
// tolua_end
|
// tolua_end
|
||||||
|
@ -389,12 +379,6 @@ protected:
|
||||||
XP_TO_LEVEL30 = 825
|
XP_TO_LEVEL30 = 825
|
||||||
} ;
|
} ;
|
||||||
|
|
||||||
/// Player's air level (for swimming)
|
|
||||||
int m_AirLevel;
|
|
||||||
|
|
||||||
/// used to time ticks between damage taken via drowning/suffocation
|
|
||||||
int m_AirTickTimer;
|
|
||||||
|
|
||||||
bool m_bVisible;
|
bool m_bVisible;
|
||||||
|
|
||||||
// Food-related variables:
|
// Food-related variables:
|
||||||
|
@ -490,12 +474,6 @@ protected:
|
||||||
|
|
||||||
/// Called in each tick if the player is fishing to make sure the floater dissapears when the player doesn't have a fishing rod as equipped item.
|
/// Called in each tick if the player is fishing to make sure the floater dissapears when the player doesn't have a fishing rod as equipped item.
|
||||||
void HandleFloater(void);
|
void HandleFloater(void);
|
||||||
|
|
||||||
/// Called in each tick to handle air-related processing i.e. drowning
|
|
||||||
void HandleAir();
|
|
||||||
|
|
||||||
/// Called once per tick to set IsSwimming and IsSubmerged
|
|
||||||
void SetSwimState(cChunk & a_Chunk);
|
|
||||||
|
|
||||||
/// Adds food exhaustion based on the difference between Pos and LastPos, sprinting status and swimming (in water block)
|
/// Adds food exhaustion based on the difference between Pos and LastPos, sprinting status and swimming (in water block)
|
||||||
void ApplyFoodExhaustionFromMovement();
|
void ApplyFoodExhaustionFromMovement();
|
||||||
|
|
|
@ -18,6 +18,10 @@ public:
|
||||||
CLASS_PROTODEF(cIronGolem);
|
CLASS_PROTODEF(cIronGolem);
|
||||||
|
|
||||||
virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = NULL) override;
|
virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = NULL) override;
|
||||||
|
|
||||||
|
// Iron golems do not drown
|
||||||
|
virtual void HandleAir(void) override {}
|
||||||
|
virtual void SetSwimState(cChunk & a_Chunk) override {}
|
||||||
} ;
|
} ;
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,9 @@ public:
|
||||||
|
|
||||||
virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = NULL) override;
|
virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = NULL) override;
|
||||||
|
|
||||||
|
// Squids do not drown (or float)
|
||||||
|
virtual void HandleAir(void) override {}
|
||||||
|
virtual void SetSwimState(cChunk & a_Chunk) override {}
|
||||||
} ;
|
} ;
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user