1
0

Merge branch 'master' into VillageGen

This commit is contained in:
madmaxoft 2014-05-27 22:09:30 +02:00
commit dff71823d4
14 changed files with 158 additions and 80 deletions

View File

@ -39,7 +39,8 @@ pre
body
{
min-width: 800px;
min-width: 400px;
max-width: 1200px;
width: 95%;
margin: 10px auto;
background-color: white;

@ -1 +1 @@
Subproject commit 5c8557d4fdfa580c100510cde07a1a778ea2e244
Subproject commit 3790f78d3f7503ff33a423b8e73e81a275562783

View File

@ -1,38 +1,39 @@
MCServer
MCServer [![Build Status](http://img.shields.io/travis/mc-server/MCServer.svg)](https://travis-ci.org/mc-server/MCServer) [![Support via Gittip](http://img.shields.io/gittip/mcs_team.svg)](https://www.gittip.com/mcs_team) [![tip for next commit](http://tip4commit.com/projects/74.svg)](http://tip4commit.com/projects/74)
========
**Current Protocol Supported:** Minecraft v1.2 -> v1.7
MCServer is a performant C++ Minecraft server designed for use in memory and cpu-limited places, or just to make regular server perform better.
MCServer is a Minecraft server that is written in C++ and designed to be efficient with memory and CPU, as well as having a flexible Lua Plugin API.
MCServer can run on PCs, Macs, and *nix. This includes android phones and tablets as well as Raspberry Pis.
We currently support the protocol from Minecraft 1.2 all the way up to Minecraft 1.7.9.
Installation
------------
To install MCServer, you can either download the repository and compile it, or download a pre-compiled version.
Normally, you will want to download a pre-compiled version of MCServer from one of the buildservers:
If you've cloned the repository using Git, you need to pull down the submodules (core plugins, some dependencies). This can be achieved with `git submodule init` and then on a regular basis (to keep up to date) `git submodule update`.
* [Linux and Raspberry Pi](http://ci.bearbin.net) (Bearbin's CI Server)
* [Windows](http://mc-server.xoft.cz) (xoft's nightly build service)
If you downloaded a ZIP file of the sources instead, you will need to download PolarSSL, too, from https://github.com/polarssl/polarssl , and unpack it into the `lib/polarssl` folder. You will also need to manually download all the plugins that you want included.
You simply need to download and extract these files before you can use the server.
Compilation instructions are available in the COMPILING file.
Linux builds can be downloaded from [Bearbin's CI Service](http://ci.bearbin.net) and Windows builds from xoft's [nightly build service](http://mc-server.xoft.cz).
After you've extracted the files, simply run the MCServer executable.
If you're a more advanced user, you may want to compile the server yourself for more performance. See the [COMPILING.md](https://github.com/mc-server/MCServer/blob/master/COMPILING.md) file for more details.
Contributing
------------
MCServer is licensed under the Apache license V2, and we welcome anybody to fork and submit a Pull Request back with their changes, and if you want to join as a permanent member we can add you to the team.
Check out the [CONTRIBUTING.md](https://github.com/mc-server/MCServer/blob/master/CONTRIBUTING.md) file for more details.
Other Stuff
-----------
For other stuff, including plugins and discussion, check the [forums](http://forum.mc-server.org) and [wiki](http://wiki.mc-server.org/).
For other stuff, including plugins and discussion, check the [forums](http://forum.mc-server.org) and [Plugin API](http://mc-server.xoft.cz/LuaAPI/).
Earn bitcoins for commits or donate to reward the MCServer developers: [![tip for next commit](http://tip4commit.com/projects/74.svg)](http://tip4commit.com/projects/74)
Travis CI: [![Build Status](https://travis-ci.org/mc-server/MCServer.png?branch=master)](https://travis-ci.org/mc-server/MCServer)
Support Us on Gittip: [![Support via Gittip](http://img.shields.io/gittip/mcs_team.svg)](https://www.gittip.com/mcs_team)
Travis CI: [![Build Status](http://img.shields.io/travis/mc-server/MCServer.svg)](https://travis-ci.org/mc-server/MCServer)

View File

@ -1391,28 +1391,8 @@ void cClientHandle::HandlePlayerLook(float a_Rotation, float a_Pitch, bool a_IsO
void cClientHandle::HandlePlayerMoveLook(double a_PosX, double a_PosY, double a_PosZ, double a_Stance, float a_Rotation, float a_Pitch, bool a_IsOnGround)
{
if ((m_Player == NULL) || (m_State != csPlaying))
{
// The client hasn't been spawned yet and sends nonsense, we know better
return;
}
/*
// TODO: Invalid stance check
if ((a_PosY >= a_Stance) || (a_Stance > a_PosY + 1.65))
{
LOGD("Invalid stance");
SendPlayerMoveLook();
return;
}
*/
m_Player->MoveTo(Vector3d(a_PosX, a_PosY, a_PosZ));
m_Player->SetStance (a_Stance);
m_Player->SetTouchGround(a_IsOnGround);
m_Player->SetHeadYaw (a_Rotation);
m_Player->SetYaw (a_Rotation);
m_Player->SetPitch (a_Pitch);
HandlePlayerLook(a_Rotation, a_Pitch, a_IsOnGround);
HandlePlayerPos(a_PosX, a_PosY, a_PosZ, a_Stance, a_IsOnGround);
}

View File

@ -80,9 +80,9 @@ public:
static AString GenerateOfflineUUID(const AString & a_Username); // tolua_export
/** Formats the type of message with the proper color and prefix for sending to the client. **/
AString FormatMessageType(bool ShouldAppendChatPrefixes, eMessageType a_ChatPrefix, const AString & a_AdditionalData);
static AString FormatMessageType(bool ShouldAppendChatPrefixes, eMessageType a_ChatPrefix, const AString & a_AdditionalData);
AString FormatChatPrefix(bool ShouldAppendChatPrefixes, AString a_ChatPrefixS, AString m_Color1, AString m_Color2);
static AString FormatChatPrefix(bool ShouldAppendChatPrefixes, AString a_ChatPrefixS, AString m_Color1, AString m_Color2);
void Kick(const AString & a_Reason); // tolua_export
void Authenticate(const AString & a_Name, const AString & a_UUID); // Called by cAuthenticator when the user passes authentication

View File

@ -189,6 +189,15 @@ void cCompositeChat::AddSuggestCommandPart(const AString & a_Text, const AString
void cCompositeChat::AddShowAchievementPart(const AString & a_PlayerName, const AString & a_Achievement, const AString & a_Style)
{
m_Parts.push_back(new cShowAchievementPart(a_PlayerName, a_Achievement, a_Style));
}
void cCompositeChat::ParseText(const AString & a_ParseText)
{
size_t len = a_ParseText.length();
@ -290,9 +299,10 @@ void cCompositeChat::ParseText(const AString & a_ParseText)
void cCompositeChat::SetMessageType(eMessageType a_MessageType)
void cCompositeChat::SetMessageType(eMessageType a_MessageType, const AString & a_AdditionalMessageTypeData)
{
m_MessageType = a_MessageType;
m_AdditionalMessageTypeData = a_AdditionalMessageTypeData;
}
@ -476,3 +486,16 @@ cCompositeChat::cSuggestCommandPart::cSuggestCommandPart(const AString & a_Text,
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// cCompositeChat::cShowAchievementPart:
cCompositeChat::cShowAchievementPart::cShowAchievementPart(const AString & a_PlayerName, const AString & a_Achievement, const AString & a_Style) :
super(ptShowAchievement, a_Achievement, a_Style),
m_PlayerName(a_PlayerName)
{
}

View File

@ -38,6 +38,7 @@ public:
ptUrl,
ptRunCommand,
ptSuggestCommand,
ptShowAchievement,
} ;
class cBasePart
@ -46,6 +47,7 @@ public:
ePartType m_PartType;
AString m_Text;
AString m_Style;
AString m_AdditionalStyleData;
cBasePart(ePartType a_PartType, const AString & a_Text, const AString & a_Style = "");
@ -107,6 +109,15 @@ public:
cSuggestCommandPart(const AString & a_Text, const AString & a_Command, const AString & a_Style = "");
} ;
class cShowAchievementPart :
public cBasePart
{
typedef cBasePart super;
public:
AString m_PlayerName;
cShowAchievementPart(const AString & a_PlayerName, const AString & a_Achievement, const AString & a_Style = "");
} ;
typedef std::vector<cBasePart *> cParts;
// tolua_begin
@ -149,12 +160,19 @@ public:
The default style is underlined yellow text. */
void AddSuggestCommandPart(const AString & a_Text, const AString & a_SuggestedCommand, const AString & a_Style = "u@b");
/** Adds a part that fully formats a specified achievement using client translatable strings
Takes achievement name and player awarded to. Displays as {player} has earned the achievement {achievement_name}.
*/
void AddShowAchievementPart(const AString & a_PlayerName, const AString & a_Achievement, const AString & a_Style = "");
/** Parses text into various parts, adds those.
Recognizes "http:" and "https:" URLs and @color-codes. */
void ParseText(const AString & a_ParseText);
/** Sets the message type, which is indicated by prefixes added to the message when serializing. */
void SetMessageType(eMessageType a_MessageType);
/** Sets the message type, which is indicated by prefixes added to the message when serializing
Takes optional AdditionalMessageTypeData to set m_AdditionalMessageTypeData. See said variable for more documentation.
*/
void SetMessageType(eMessageType a_MessageType, const AString & a_AdditionalMessageTypeData = "");
/** Adds the "underline" style to each part that is an URL. */
void UnderlineUrls(void);
@ -164,6 +182,9 @@ public:
/** Returns the message type set previously by SetMessageType(). */
eMessageType GetMessageType(void) const { return m_MessageType; }
/** Returns additional data pertaining to message type, for example, the name of a mtPrivateMsg sender */
AString GetAdditionalMessageTypeData(void) const { return m_AdditionalMessageTypeData; }
/** Returns the text from the parts that comprises the human-readable data.
Used for older protocols that don't support composite chat
and for console-logging. */
@ -184,6 +205,9 @@ protected:
/** The message type, as indicated by prefixes. */
eMessageType m_MessageType;
/** Additional data pertaining to message type, for example, the name of a mtPrivateMsg sender */
AString m_AdditionalMessageTypeData;
/** Adds a_AddStyle to a_Style; overwrites the existing style if appropriate.
If the style already contains something that a_AddStyle overrides, it is erased first. */

View File

@ -1018,16 +1018,17 @@ void cEntity::TickInVoid(cChunk & a_Chunk)
void cEntity::DetectCacti()
void cEntity::DetectCacti(void)
{
int X = POSX_TOINT, Y = POSY_TOINT, Z = POSZ_TOINT;
float w = m_Width / 2;
double w = m_Width / 2;
if (
(((X + 1) - GetPosX() < w) && (GetWorld()->GetBlock(X + 1, Y, Z) == E_BLOCK_CACTUS)) ||
(((GetPosX() - (X - 1)) - 1 < w) && (GetWorld()->GetBlock(X - 1, Y, Z) == E_BLOCK_CACTUS)) ||
((Y > 0) && (Y < cChunkDef::Height)) &&
((((X + 1) - GetPosX() < w) && (GetWorld()->GetBlock(X + 1, Y, Z) == E_BLOCK_CACTUS)) ||
((GetPosX() - X < w) && (GetWorld()->GetBlock(X - 1, Y, Z) == E_BLOCK_CACTUS)) ||
(((Z + 1) - GetPosZ() < w) && (GetWorld()->GetBlock(X, Y, Z + 1) == E_BLOCK_CACTUS)) ||
(((GetPosZ() - (Z - 1)) - 1 < w) && (GetWorld()->GetBlock(X, Y, Z - 1) == E_BLOCK_CACTUS)) ||
(((Y > 0) && (Y < cChunkDef::Height)) && ((GetPosY() - Y < 1) && (GetWorld()->GetBlock(X, Y, Z) == E_BLOCK_CACTUS)))
((GetPosZ() - Z < w) && (GetWorld()->GetBlock(X, Y, Z - 1) == E_BLOCK_CACTUS)) ||
(((GetPosY() - Y < 1) && (GetWorld()->GetBlock(X, Y, Z) == E_BLOCK_CACTUS))))
)
{
TakeDamage(dtCactusContact, NULL, 1, 0);

View File

@ -377,7 +377,7 @@ short cPlayer::DeltaExperience(short a_Xp_delta)
}
LOGD("Player \"%s\" gained/lost %d experience, total is now: %d",
m_PlayerName.c_str(), a_Xp_delta, m_CurrentXp);
GetName().c_str(), a_Xp_delta, m_CurrentXp);
// Set experience to be updated
m_bDirtyExperience = true;
@ -391,7 +391,7 @@ short cPlayer::DeltaExperience(short a_Xp_delta)
void cPlayer::StartChargingBow(void)
{
LOGD("Player \"%s\" started charging their bow", m_PlayerName.c_str());
LOGD("Player \"%s\" started charging their bow", GetName().c_str());
m_IsChargingBow = true;
m_BowCharge = 0;
}
@ -402,7 +402,7 @@ void cPlayer::StartChargingBow(void)
int cPlayer::FinishChargingBow(void)
{
LOGD("Player \"%s\" finished charging their bow at a charge of %d", m_PlayerName.c_str(), m_BowCharge);
LOGD("Player \"%s\" finished charging their bow at a charge of %d", GetName().c_str(), m_BowCharge);
int res = m_BowCharge;
m_IsChargingBow = false;
m_BowCharge = 0;
@ -415,7 +415,7 @@ int cPlayer::FinishChargingBow(void)
void cPlayer::CancelChargingBow(void)
{
LOGD("Player \"%s\" cancelled charging their bow at a charge of %d", m_PlayerName.c_str(), m_BowCharge);
LOGD("Player \"%s\" cancelled charging their bow at a charge of %d", GetName().c_str(), m_BowCharge);
m_IsChargingBow = false;
m_BowCharge = 0;
}
@ -1179,8 +1179,8 @@ unsigned int cPlayer::AwardAchievement(const eStatistic a_Ach)
{
// First time, announce it
cCompositeChat Msg;
Msg.AddTextPart(m_PlayerName + " has just earned the achievement ");
Msg.AddTextPart(cStatInfo::GetName(a_Ach)); // TODO 2014-05-12 xdot: Use the proper cCompositeChat part (cAchievement)
Msg.SetMessageType(mtSuccess);
Msg.AddShowAchievementPart(GetName(), cStatInfo::GetName(a_Ach));
m_World->BroadcastChat(Msg);
// Increment the statistic
@ -1315,7 +1315,7 @@ void cPlayer::AddToGroup( const AString & a_GroupName )
{
cGroup* Group = cRoot::Get()->GetGroupManager()->GetGroup( a_GroupName );
m_Groups.push_back( Group );
LOGD("Added %s to group %s", m_PlayerName.c_str(), a_GroupName.c_str() );
LOGD("Added %s to group %s", GetName().c_str(), a_GroupName.c_str() );
ResolveGroups();
ResolvePermissions();
}
@ -1339,13 +1339,13 @@ void cPlayer::RemoveFromGroup( const AString & a_GroupName )
if( bRemoved )
{
LOGD("Removed %s from group %s", m_PlayerName.c_str(), a_GroupName.c_str() );
LOGD("Removed %s from group %s", GetName().c_str(), a_GroupName.c_str() );
ResolveGroups();
ResolvePermissions();
}
else
{
LOGWARN("Tried to remove %s from group %s but was not in that group", m_PlayerName.c_str(), a_GroupName.c_str() );
LOGWARN("Tried to remove %s from group %s but was not in that group", GetName().c_str(), a_GroupName.c_str() );
}
}
@ -1451,7 +1451,7 @@ void cPlayer::ResolveGroups()
if( AllGroups.find( CurrentGroup ) != AllGroups.end() )
{
LOGWARNING("ERROR: Player \"%s\" is in the group multiple times (\"%s\"). Please fix your settings in users.ini!",
m_PlayerName.c_str(), CurrentGroup->GetName().c_str()
GetName().c_str(), CurrentGroup->GetName().c_str()
);
}
else
@ -1463,7 +1463,7 @@ void cPlayer::ResolveGroups()
{
if( AllGroups.find( *itr ) != AllGroups.end() )
{
LOGERROR("ERROR: Player %s is in the same group multiple times due to inheritance (%s). FIX IT!", m_PlayerName.c_str(), (*itr)->GetName().c_str() );
LOGERROR("ERROR: Player %s is in the same group multiple times due to inheritance (%s). FIX IT!", GetName().c_str(), (*itr)->GetName().c_str() );
continue;
}
ToIterate.push_back( *itr );
@ -1615,19 +1615,19 @@ void cPlayer::LoadPermissionsFromDisk()
cIniFile IniFile;
if (IniFile.ReadFile("users.ini"))
{
AString Groups = IniFile.GetValueSet(m_PlayerName, "Groups", "Default");
AString Groups = IniFile.GetValueSet(GetName(), "Groups", "Default");
AStringVector Split = StringSplitAndTrim(Groups, ",");
for (AStringVector::const_iterator itr = Split.begin(), end = Split.end(); itr != end; ++itr)
{
if (!cRoot::Get()->GetGroupManager()->ExistsGroup(*itr))
{
LOGWARNING("The group %s for player %s was not found!", itr->c_str(), m_PlayerName.c_str());
LOGWARNING("The group %s for player %s was not found!", itr->c_str(), GetName().c_str());
}
AddToGroup(*itr);
}
AString Color = IniFile.GetValue(m_PlayerName, "Color", "-");
AString Color = IniFile.GetValue(GetName(), "Color", "-");
if (!Color.empty())
{
m_Color = Color[0];
@ -1636,7 +1636,7 @@ void cPlayer::LoadPermissionsFromDisk()
else
{
cGroupManager::GenerateDefaultUsersIni(IniFile);
IniFile.AddValue("Groups", m_PlayerName, "Default");
IniFile.AddValue("Groups", GetName(), "Default");
AddToGroup("Default");
}
IniFile.WriteFile("users.ini");
@ -1651,14 +1651,14 @@ bool cPlayer::LoadFromDisk()
LoadPermissionsFromDisk();
// Log player permissions, cause it's what the cool kids do
LOGINFO("Player %s has permissions:", m_PlayerName.c_str() );
LOGINFO("Player %s has permissions:", GetName().c_str() );
for( PermissionMap::iterator itr = m_ResolvedPermissions.begin(); itr != m_ResolvedPermissions.end(); ++itr )
{
if( itr->second ) LOG(" - %s", itr->first.c_str() );
}
AString SourceFile;
Printf(SourceFile, "players/%s.json", m_PlayerName.c_str() );
Printf(SourceFile, "players/%s.json", GetName().c_str() );
cFile f;
if (!f.Open(SourceFile, cFile::fmRead))
@ -1726,7 +1726,7 @@ bool cPlayer::LoadFromDisk()
StatSerializer.Load();
LOGD("Player \"%s\" was read from file, spawning at {%.2f, %.2f, %.2f} in world \"%s\"",
m_PlayerName.c_str(), GetPosX(), GetPosY(), GetPosZ(), m_LoadedWorldName.c_str()
GetName().c_str(), GetPosX(), GetPosY(), GetPosZ(), m_LoadedWorldName.c_str()
);
return true;
@ -1782,12 +1782,12 @@ bool cPlayer::SaveToDisk()
std::string JsonData = writer.write(root);
AString SourceFile;
Printf(SourceFile, "players/%s.json", m_PlayerName.c_str() );
Printf(SourceFile, "players/%s.json", GetName().c_str() );
cFile f;
if (!f.Open(SourceFile, cFile::fmWrite))
{
LOGERROR("ERROR WRITING PLAYER \"%s\" TO FILE \"%s\" - cannot open file", m_PlayerName.c_str(), SourceFile.c_str());
LOGERROR("ERROR WRITING PLAYER \"%s\" TO FILE \"%s\" - cannot open file", GetName().c_str(), SourceFile.c_str());
return false;
}
if (f.Write(JsonData.c_str(), JsonData.size()) != (int)JsonData.size())
@ -1798,10 +1798,10 @@ bool cPlayer::SaveToDisk()
// Save the player stats.
// We use the default world name (like bukkit) because stats are shared between dimensions/worlds.
cStatSerializer StatSerializer(cRoot::Get()->GetDefaultWorld()->GetName(), m_PlayerName, &m_Stats);
cStatSerializer StatSerializer(cRoot::Get()->GetDefaultWorld()->GetName(), GetName(), &m_Stats);
if (!StatSerializer.Save())
{
LOGERROR("Could not save stats for player %s", m_PlayerName.c_str());
LOGERROR("Could not save stats for player %s", GetName().c_str());
return false;
}

View File

@ -50,6 +50,15 @@ cGridStructGen::cGridStructGen(
m_MaxStructureSizeZ(a_MaxStructureSizeZ),
m_MaxCacheSize(a_MaxCacheSize)
{
size_t NumStructuresPerQuery = (size_t)((m_MaxStructureSizeX / m_GridSizeX + 1) * (m_MaxStructureSizeZ / m_GridSizeZ + 1));
if (NumStructuresPerQuery > m_MaxCacheSize)
{
m_MaxCacheSize = NumStructuresPerQuery * 4;
LOGINFO(
"cGridStructGen: The cache size is too small (%u), increasing the cache size to %u to avoid inefficiency.",
(unsigned)a_MaxCacheSize, (unsigned)m_MaxCacheSize
);
}
}

View File

@ -1283,7 +1283,7 @@ cStructGenMineShafts::cStructGenMineShafts(
int a_Seed, int a_GridSize, int a_MaxSystemSize,
int a_ChanceCorridor, int a_ChanceCrossing, int a_ChanceStaircase
) :
super(a_Seed, a_GridSize, a_GridSize, 120 + a_MaxSystemSize * 10, 120 + a_MaxSystemSize * 10, 100),
super(a_Seed, a_GridSize, a_GridSize, a_MaxSystemSize, a_MaxSystemSize, 100),
m_Noise(a_Seed),
m_GridSize(a_GridSize),
m_MaxSystemSize(a_MaxSystemSize),

View File

@ -45,8 +45,14 @@ cGroupManager::cGroupManager()
{
LOGD("-- Loading Groups --");
LoadGroups();
CheckUsers();
if (!LoadGroups())
{
LOGWARNING("ERROR: Groups could not load!");
}
if (!CheckUsers())
{
LOGWARNING("ERROR: User file could not be found!");
}
LOGD("-- Groups Successfully Loaded --");
}
@ -70,13 +76,13 @@ void cGroupManager::GenerateDefaultUsersIni(cIniFile & a_IniFile)
void cGroupManager::CheckUsers(void)
bool cGroupManager::CheckUsers()
{
cIniFile IniFile;
if (!IniFile.ReadFile("users.ini"))
{
GenerateDefaultUsersIni(IniFile);
return;
return true;
}
int NumKeys = IniFile.GetNumKeys();
@ -97,13 +103,15 @@ void cGroupManager::CheckUsers(void)
}
} // for itr - Split[]
} // for i - ini file keys
// Always return true for now, just but we can handle writefile fails later.
return true;
}
void cGroupManager::LoadGroups()
bool cGroupManager::LoadGroups()
{
cIniFile IniFile;
if (!IniFile.ReadFile("groups.ini"))
@ -180,6 +188,8 @@ void cGroupManager::LoadGroups()
}
}
}
// Always return true, we can handle writefile fails later.
return true;
}

View File

@ -16,8 +16,8 @@ class cGroupManager
public:
bool ExistsGroup(const AString & a_Name);
cGroup * GetGroup(const AString & a_Name);
void LoadGroups(void);
void CheckUsers(void);
bool LoadGroups();
bool CheckUsers();
/** Writes the default header to the specified ini file, and saves it as "users.ini". */
static void GenerateDefaultUsersIni(cIniFile & a_IniFile);

View File

@ -234,7 +234,7 @@ void cProtocol172::SendChat(const cCompositeChat & a_Message)
// Compose the complete Json string to send:
Json::Value msg;
msg["text"] = ""; // The client crashes without this
msg["text"] = cClientHandle::FormatMessageType(m_Client->GetPlayer()->GetWorld()->ShouldUseChatPrefixes(), a_Message.GetMessageType(), a_Message.GetAdditionalMessageTypeData()); // The client crashes without this field being present
const cCompositeChat::cParts & Parts = a_Message.GetParts();
for (cCompositeChat::cParts::const_iterator itr = Parts.begin(), end = Parts.end(); itr != end; ++itr)
{
@ -289,6 +289,35 @@ void cProtocol172::SendChat(const cCompositeChat & a_Message)
AddChatPartStyle(Part, p.m_Style);
break;
}
case cCompositeChat::ptShowAchievement:
{
const cCompositeChat::cShowAchievementPart & p = (const cCompositeChat::cShowAchievementPart &)**itr;
Part["translate"] = "chat.type.achievement";
Json::Value Ach;
Ach["action"] = "show_achievement";
Ach["value"] = p.m_Text;
Json::Value AchColourAndName;
AchColourAndName["color"] = "green";
AchColourAndName["translate"] = p.m_Text;
AchColourAndName["hoverEvent"] = Ach;
Json::Value Extra;
Extra.append(AchColourAndName);
Json::Value Name;
Name["text"] = p.m_PlayerName;
Json::Value With;
With.append(Name);
With.append(Extra);
Part["with"] = With;
AddChatPartStyle(Part, p.m_Style);
break;
}
}
msg["extra"].append(Part);
} // for itr - Parts[]