1277 lines
36 KiB
C++
1277 lines
36 KiB
C++
// Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
|
|
// Copyright (C) 2010 Winch Gate Property Limited
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as
|
|
// published by the Free Software Foundation, either version 3 of the
|
|
// License, or (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
#include "stdafx.h"
|
|
#include "nel/misc/time_nl.h"
|
|
#include "plugin.h"
|
|
#include "resource.h"
|
|
#include "nel/misc/entity_id.h"
|
|
#include "nel/misc/geom_ext.h"
|
|
#include "nel/misc/time_nl.h"
|
|
#include "DialogLogin.h"
|
|
#include <deque>
|
|
|
|
using namespace NLMISC;
|
|
using namespace NLLIGO;
|
|
using namespace NLSOUND;
|
|
using namespace NLNET;
|
|
using namespace std;
|
|
|
|
CFileDisplayer *ShardMonitorPluginLogDisplayer= NULL;
|
|
|
|
// vl: important to add the next line or AfxGetApp() will returns 0 after AFX_MANAGE_STATE(AfxGetStaticModuleState());
|
|
CWinApp theApp;
|
|
|
|
|
|
// ***************************************************************************
|
|
extern "C"
|
|
{
|
|
void *createPlugin()
|
|
{
|
|
return new CPlugin();
|
|
}
|
|
}
|
|
|
|
// ***************************************************************************
|
|
|
|
CPlugin::CPlugin()
|
|
{
|
|
AFX_MANAGE_STATE(AfxGetStaticModuleState());
|
|
|
|
NLMISC::createDebug();
|
|
ShardMonitorPluginLogDisplayer= new CFileDisplayer("world_editor_shard_monitor_plugin.log", true, "WORLD_EDITOR_SHARD_MONITOR_PLUGIN.LOG");
|
|
DebugLog->addDisplayer (ShardMonitorPluginLogDisplayer);
|
|
InfoLog->addDisplayer (ShardMonitorPluginLogDisplayer);
|
|
WarningLog->addDisplayer (ShardMonitorPluginLogDisplayer);
|
|
ErrorLog->addDisplayer (ShardMonitorPluginLogDisplayer);
|
|
AssertLog->addDisplayer (ShardMonitorPluginLogDisplayer);
|
|
|
|
nlinfo("Starting shard monitor plugin...");
|
|
|
|
_DialogFlag = new CDialogFlags(NULL);
|
|
m_Initialized = false;
|
|
_Client = NULL;
|
|
_Root = NULL;
|
|
_RequestedDownload = 0;
|
|
_EntityDisplayMode = EntityType;
|
|
_DisplayHits = true;
|
|
|
|
_EntityIcons = NULL;
|
|
|
|
_CloseUpDisplayDistance = 10.f;
|
|
|
|
std::fill(_CloseUpFlags, _CloseUpFlags + sizeofarray(_CloseUpFlags), true);
|
|
|
|
_ConnectState = Disconnected;
|
|
|
|
}
|
|
|
|
// ***************************************************************************
|
|
|
|
CPlugin::~CPlugin()
|
|
{
|
|
if (m_Initialized)
|
|
{
|
|
_PluginAccess->deleteRootPluginPrimitive();
|
|
_Root = NULL;
|
|
m_Initialized = false;
|
|
if (_PluginAccess)
|
|
{
|
|
_PluginAccess->deleteTexture(_EntityIcons);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ***************************************************************************
|
|
void CPlugin::progress (float progressValue)
|
|
{
|
|
}
|
|
|
|
// ***************************************************************************
|
|
|
|
void CPlugin::removeEntity (CEntityEntry &entity)
|
|
{
|
|
// Changes ?
|
|
if (entity.Primitive)
|
|
{
|
|
std::map<NLLIGO::IPrimitive*, uint32>::iterator ite = _PrimitiveToString.find (entity.Primitive);
|
|
if(ite != _PrimitiveToString.end())
|
|
{
|
|
uint32 stringId = (*ite).second;
|
|
_PrimitiveToString.erase(entity.Primitive);
|
|
|
|
std::multimap<uint32, NLLIGO::IPrimitive*>::iterator ite2 = _StringToPrimitive.find (stringId);
|
|
while ((ite2 != _StringToPrimitive.end()) && (ite2->first == stringId))
|
|
{
|
|
std::multimap<uint32, NLLIGO::IPrimitive*>::iterator ite2delete = ite2;
|
|
ite2++;
|
|
_StringToPrimitive.erase(ite2delete);
|
|
}
|
|
}
|
|
|
|
_PluginAccess->deletePluginPrimitive (entity.Primitive);
|
|
entity.Primitive = NULL;
|
|
entity.ColorDirty = true;
|
|
}
|
|
}
|
|
|
|
/** Map a color of an entity to its aspect (color & icon)
|
|
* \param di array giving the aspect for several value of the property.
|
|
* \param value value for which aspect is queried
|
|
* \param defaultValue value of the property that gives the default aspect
|
|
*/
|
|
static void getEntityColorFromValue(const TEntityDisplayInfoVect &di, uint value, uint defaultValue, CRGBA &destColor)
|
|
{
|
|
CRGBA defaultCol(127, 127, 127);
|
|
for(uint k = 0; k < di.size(); ++k)
|
|
{
|
|
if (di[k].Value == defaultValue)
|
|
{
|
|
defaultCol = di[k].Color;
|
|
}
|
|
else if (di[k].Value == value)
|
|
{
|
|
destColor = di[k].Color;
|
|
return;
|
|
}
|
|
}
|
|
destColor = defaultCol;
|
|
}
|
|
|
|
|
|
static void getEntityIconFromValue(const TEntityDisplayInfoVect &di, uint value, uint defaultValue, CEntityIcon &destIcon)
|
|
{
|
|
CEntityIcon defaultIcon;
|
|
for(uint k = 0; k < di.size(); ++k)
|
|
{
|
|
if (di[k].Value == defaultValue)
|
|
{
|
|
defaultIcon = di[k].Icon;
|
|
}
|
|
else if (di[k].Value == value)
|
|
{
|
|
destIcon = di[k].Icon;
|
|
return;
|
|
}
|
|
}
|
|
destIcon = defaultIcon;
|
|
}
|
|
|
|
static bool getEntityVisibilityFromValue(const TEntityDisplayInfoVect &di, uint value)
|
|
{
|
|
for(uint k = 0; k < di.size(); ++k)
|
|
{
|
|
if (di[k].Value == value)
|
|
{
|
|
return di[k].Visible;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// ***************************************************************************
|
|
void CPlugin::updateEntityAspect(CEntityEntry &entity)
|
|
{
|
|
if (!entity.Primitive) return;
|
|
NLMISC::CRGBA color;
|
|
bool visible = true;
|
|
const TEntityDisplayInfoVect &di = _DialogFlag->getEntityDisplayInfos(_EntityDisplayMode);
|
|
// update icons
|
|
getEntityIconFromValue(_DialogFlag->getEntityDisplayInfos(EntityType), (uint) entity.EntityId.getType(), RYZOMID::unknown, entity.IconForEntityType);
|
|
getEntityIconFromValue(_DialogFlag->getEntityDisplayInfos(EntityMode), (uint) entity.Mode, MBEHAV::UNKNOWN_MODE, entity.IconForEntityMode);
|
|
//
|
|
switch(_EntityDisplayMode)
|
|
{
|
|
case EntityType:
|
|
getEntityColorFromValue(di, (uint) entity.EntityId.getType(), RYZOMID::unknown, color);
|
|
visible = getEntityVisibilityFromValue(di, (uint) entity.EntityId.getType());
|
|
break;
|
|
case EntityMode:
|
|
getEntityColorFromValue(di, (uint) entity.Mode, MBEHAV::UNKNOWN_MODE, color);
|
|
visible = getEntityVisibilityFromValue(di, (uint) entity.EntityId.getType());
|
|
break;
|
|
case EntityHitPoints:
|
|
{
|
|
if (entity.MaxHitPoints == 0)
|
|
{
|
|
color = di[3].Color;
|
|
}
|
|
else
|
|
{
|
|
float ratio = (float) entity.HitPoints / (float) entity.MaxHitPoints;
|
|
if (ratio > 0.5f)
|
|
{
|
|
color.blendFromui(di[1].Color, di[2].Color, (uint8) (255.f * 2.f * (ratio - 0.5f)));
|
|
}
|
|
else
|
|
{
|
|
color.blendFromui(di[0].Color, di[1].Color, (uint8) (255.f * 2.f * ratio));
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case EntityAlive:
|
|
{
|
|
if (entity.MaxHitPoints == 0)
|
|
{
|
|
color = di[2].Color;
|
|
}
|
|
else
|
|
{
|
|
if (entity.HitPoints == 0) color = di[0].Color;
|
|
else color = di[1].Color;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
nlassert(0);
|
|
break;
|
|
}
|
|
if (color != entity.CurrentColor || entity.ColorDirty)
|
|
{
|
|
nlassert(entity.Primitive);
|
|
entity.Primitive->removePropertyByName ("Color");
|
|
entity.Primitive->addPropertyByName ("Color", new CPropertyColor(color));
|
|
_PluginAccess->invalidatePluginPrimitive (entity.Primitive, QuadTree);
|
|
entity.CurrentColor = color;
|
|
entity.ColorDirty = false;
|
|
}
|
|
entity.Hidden = !visible;
|
|
_PluginAccess->setPrimitiveHideFlag(*entity.Primitive, !visible);
|
|
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
void serverSentAdd (CMessage &msgin, TSockId from, CCallbackNetBase &netbase)
|
|
{
|
|
// Get the plugin
|
|
CPlugin *plugin = (CPlugin *)(from->appId());
|
|
|
|
// Read the message version
|
|
// Version 1 : added sheet id
|
|
sint version = msgin.serialVersion(1);
|
|
|
|
// Called when the server sent a ADD message
|
|
uint32 count;
|
|
msgin.serial(count);
|
|
|
|
for (uint i = 0;i < count; i++)
|
|
{
|
|
uint32 id;
|
|
uint32 stringId;
|
|
CEntityId entityId;
|
|
NLMISC::CSheetId sheetID;
|
|
msgin.serial(id);
|
|
msgin.serial(stringId);
|
|
msgin.serial (entityId);
|
|
if (version >= 1)
|
|
{
|
|
msgin.serial(sheetID);
|
|
}
|
|
|
|
// Resize the player array
|
|
if (id >= plugin->_Entites.size())
|
|
plugin->_Entites.resize (id+1);
|
|
|
|
std::vector<CPrimitiveClass::CInitParameters> Parameters;
|
|
plugin->_Entites[id].Primitive = (CPrimPoint*)(plugin->_PluginAccess->createPluginPrimitive ("player", "", NLMISC::CVector::Null, 0, Parameters, plugin->_Root));
|
|
plugin->_Entites[id].EntityId = entityId;
|
|
|
|
if (plugin->_Entites[id].Primitive)
|
|
{
|
|
if (stringId)
|
|
{
|
|
plugin->_PrimitiveToString.insert (std::map<NLLIGO::IPrimitive*, uint32>::value_type (plugin->_Entites[id].Primitive, stringId));
|
|
plugin->_StringToPrimitive.insert (std::multimap<uint32, NLLIGO::IPrimitive*>::value_type (stringId, plugin->_Entites[id].Primitive));
|
|
|
|
// Set the primitive name
|
|
plugin->setPrimitiveName (plugin->_Entites[id].Primitive, stringId);
|
|
}
|
|
else
|
|
{
|
|
// Set the new name
|
|
plugin->_Entites[id].Primitive->removePropertyByName ("name");
|
|
plugin->_Entites[id].Primitive->addPropertyByName ("name", new CPropertyString (sheetID.toString().c_str()));
|
|
|
|
// Invalidate the name
|
|
plugin->_PluginAccess->invalidatePluginPrimitive (plugin->_Entites[id].Primitive, LogicTreeParam);
|
|
}
|
|
|
|
// Set the entity id
|
|
plugin->_Entites[id].Primitive->removePropertyByName ("entity id");
|
|
plugin->_Entites[id].Primitive->addPropertyByName ("entity id", new CPropertyString (entityId.toString().c_str()));
|
|
|
|
// Set the sheet name
|
|
plugin->_Entites[id].Primitive->removePropertyByName ("Sheet");
|
|
plugin->_Entites[id].Primitive->addPropertyByName ("Sheet", new CPropertyString (sheetID.toString().c_str()));
|
|
|
|
// set start color
|
|
plugin->updateEntityAspect(plugin->_Entites[id]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ***************************************************************************
|
|
|
|
void serverSentPos (CMessage &msgin, TSockId from, CCallbackNetBase &netbase)
|
|
{
|
|
// Get the plugin
|
|
CPlugin *plugin = (CPlugin *)(from->appId());
|
|
|
|
// Read the message version
|
|
sint version = msgin.serialVersion(0);
|
|
|
|
// Called when the server sent a POS message
|
|
uint32 count;
|
|
msgin.serial(count);
|
|
|
|
for (uint i = 0;i < count; i++)
|
|
{
|
|
uint32 id;
|
|
msgin.serial(id);
|
|
|
|
// Read position
|
|
CPlugin::CEntityEntry &entity = plugin->_Entites[id];
|
|
float x, y, theta;
|
|
msgin.serial (x);
|
|
msgin.serial (y);
|
|
msgin.serial (theta);
|
|
|
|
// Changes ?
|
|
if (entity.Primitive)
|
|
{
|
|
if ((entity.Primitive->Point.x != x) || (entity.Primitive->Point.y != y) || (entity.Primitive->Angle != theta))
|
|
{
|
|
entity.Primitive->Point.x = x;
|
|
entity.Primitive->Point.y = y;
|
|
entity.Primitive->Angle = theta;
|
|
plugin->_PluginAccess->invalidatePluginPrimitive (entity.Primitive, QuadTree);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ***************************************************************************
|
|
|
|
void serverSentMiscProp (CMessage &msgin, TSockId from, CCallbackNetBase &netbase)
|
|
{
|
|
// Get the plugin
|
|
CPlugin *plugin = (CPlugin *)(from->appId());
|
|
|
|
// Read the message version
|
|
sint version = msgin.serialVersion(0);
|
|
|
|
// Called when the server sent a HIT_POINTS message
|
|
uint32 count;
|
|
msgin.serial(count);
|
|
|
|
for (uint i = 0;i < count; i++)
|
|
{
|
|
uint32 id;
|
|
msgin.serial(id);
|
|
|
|
// Read hitpoints
|
|
CPlugin::CEntityEntry &entity = plugin->_Entites[id];
|
|
sint32 hitPoints;
|
|
sint32 maxHitPoints;
|
|
uint8 mode;
|
|
uint8 behaviour;
|
|
msgin.serial (hitPoints);
|
|
msgin.serial (maxHitPoints);
|
|
msgin.serial(mode);
|
|
msgin.serial(behaviour);
|
|
|
|
|
|
// Changes ?
|
|
if (entity.Primitive)
|
|
{
|
|
if (hitPoints < entity.HitPoints)
|
|
{
|
|
CPlugin::CHit hit;
|
|
hit.EntityId = id;
|
|
hit.HitTime = plugin->_CurrentTime;
|
|
plugin->_HitList.push_front(hit);
|
|
}
|
|
entity.HitPoints = hitPoints;
|
|
entity.MaxHitPoints = maxHitPoints;
|
|
entity.Mode = (MBEHAV::EMode) mode;
|
|
|
|
plugin->updateEntityAspect(entity);
|
|
plugin->_Entites[id].Primitive->removePropertyByName ("HitPoints");
|
|
plugin->_Entites[id].Primitive->addPropertyByName ("HitPoints", new CPropertyString (toString(hitPoints)));
|
|
plugin->_Entites[id].Primitive->removePropertyByName ("MaxHitPoints");
|
|
plugin->_Entites[id].Primitive->addPropertyByName ("MaxHitPoints", new CPropertyString (toString(maxHitPoints)));
|
|
plugin->_Entites[id].Primitive->removePropertyByName ("Mode");
|
|
plugin->_Entites[id].Primitive->addPropertyByName ("Mode", new CPropertyString (MBEHAV::modeToString((MBEHAV::EMode) mode)));
|
|
plugin->_Entites[id].Primitive->removePropertyByName ("Behaviour");
|
|
plugin->_Entites[id].Primitive->addPropertyByName ("Behaviour", new CPropertyString (MBEHAV::behaviourToString((MBEHAV::EBehaviour) mode)));
|
|
plugin->_PluginAccess->invalidatePluginPrimitive (plugin->_Entites[id].Primitive, QuadTree);
|
|
|
|
// if the primitive is the only one that is selected, then update the property dialog
|
|
const std::list<NLLIGO::IPrimitive*> &currSel = plugin->_PluginAccess->getCurrentSelection();
|
|
if (currSel.size() == 1)
|
|
{
|
|
if (currSel.front() == plugin->_Entites[id].Primitive)
|
|
{
|
|
// properties for this primitive are currently displayed -> refresh
|
|
plugin->_PluginAccess->refreshPropertyDialog();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void serverSentParams(CMessage &msgin, TSockId from, CCallbackNetBase &netbase)
|
|
{
|
|
CPlugin *plugin = (CPlugin *)(from->appId());
|
|
CConnectionMsg cm;
|
|
cm.MsgType = CConnectionMsg::ServerParamsMsg;
|
|
msgin.serial(cm.ServerParams.Version);
|
|
msgin.serial(cm.ServerParams.LoginRequired);
|
|
plugin->setConnectionMsg(cm);
|
|
}
|
|
|
|
void serverSentAuthentValid(CMessage &msgin, TSockId from, CCallbackNetBase &netbase)
|
|
{
|
|
CPlugin *plugin = (CPlugin *)(from->appId());
|
|
CConnectionMsg cm;
|
|
cm.MsgType = CConnectionMsg::AuthentValid;
|
|
plugin->setConnectionMsg(cm);
|
|
}
|
|
|
|
void serverSentAuthentInvalid(CMessage &msgin, TSockId from, CCallbackNetBase &netbase)
|
|
{
|
|
CPlugin *plugin = (CPlugin *)(from->appId());
|
|
CConnectionMsg cm;
|
|
cm.MsgType = CConnectionMsg::AuthentInvalid;
|
|
plugin->setConnectionMsg(cm);
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
void CPlugin::authentValid()
|
|
{
|
|
if (_ConnectState == WaitingLoginConfirmation)
|
|
{
|
|
_ConnectState = Authentificated;
|
|
updateConnectionState();
|
|
}
|
|
}
|
|
|
|
// ***************************************************************************
|
|
void CPlugin::authentInvalid()
|
|
{
|
|
if (_ConnectState == WaitingLoginConfirmation)
|
|
{
|
|
if (_Client->connected())
|
|
{
|
|
connectDisconnect();
|
|
updateConnectionState();
|
|
}
|
|
MessageBox(NULL, getStringRsc(IDS_LOGIN_FAILED), getStringRsc(IDS_ERROR), MB_ICONEXCLAMATION);
|
|
}
|
|
}
|
|
|
|
// ***************************************************************************
|
|
void CPlugin::serverParamsReceived(const CServerParams &sp)
|
|
{
|
|
nlassert(sp.Version == 0); // version system not used now (since first version ...)
|
|
if (_ConnectState != WaitingServerParams) return;
|
|
if (sp.LoginRequired)
|
|
{
|
|
CDialogLogin dl;
|
|
if (dl.DoModal() == IDOK)
|
|
{
|
|
CMessage msg;
|
|
msg.setType("AUTHENT");
|
|
std::string login = (LPCTSTR)dl.m_Login;
|
|
std::string password = (LPCTSTR)dl.m_Password;
|
|
sint ver = msg.serialVersion(0);
|
|
msg.serial(login);
|
|
msg.serial(password);
|
|
_Client->send(msg);
|
|
_ConnectState = WaitingLoginConfirmation;
|
|
updateConnectionState();
|
|
}
|
|
else
|
|
{
|
|
if (_Client->connected())
|
|
{
|
|
connectDisconnect();
|
|
updateConnectionState();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// no need for a password, so we are already authentificated
|
|
_ConnectState = Authentificated;
|
|
updateConnectionState();
|
|
}
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
void CPlugin::setEntityDisplayMode(TEntityDisplayMode edm)
|
|
{
|
|
if (edm == _EntityDisplayMode) return;
|
|
_EntityDisplayMode = edm;
|
|
updateDisplay();
|
|
}
|
|
|
|
// ***************************************************************************
|
|
void CPlugin::updateDisplay()
|
|
{
|
|
for(uint k = 0; k < _Entites.size(); ++k)
|
|
{
|
|
CPlugin::CEntityEntry &entity = _Entites[k];
|
|
if (entity.Primitive)
|
|
{
|
|
updateEntityAspect(entity);
|
|
_PluginAccess->invalidatePluginPrimitive (entity.Primitive, QuadTree);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ***************************************************************************
|
|
void serverSentString (CMessage &msgin, TSockId from, CCallbackNetBase &netbase)
|
|
{
|
|
// Get the plugin
|
|
CPlugin *plugin = (CPlugin *)(from->appId());
|
|
|
|
// Read the message version
|
|
sint version = msgin.serialVersion(0);
|
|
|
|
// Called when the server sent a POS message
|
|
uint32 count;
|
|
msgin.serial(count);
|
|
|
|
for (uint i = 0;i < count; i++)
|
|
{
|
|
uint32 stringId;
|
|
string str;
|
|
msgin.serial(stringId);
|
|
msgin.serial(str);
|
|
|
|
// Set the string
|
|
plugin->_StringIdToString.insert (std::map<uint32, std::string>::value_type (stringId, str));
|
|
|
|
// Modify entites
|
|
|
|
// Changes ?
|
|
std::multimap<uint32, NLLIGO::IPrimitive*>::iterator ite = plugin->_StringToPrimitive.find (stringId);
|
|
while ((ite != plugin->_StringToPrimitive.end()) && (ite->first == stringId))
|
|
{
|
|
// Set the name
|
|
plugin->setPrimitiveName (ite->second, stringId);
|
|
|
|
ite++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ***************************************************************************
|
|
|
|
void serverSentRemove (CMessage &msgin, TSockId from, CCallbackNetBase &netbase)
|
|
{
|
|
// Get the plugin
|
|
CPlugin *plugin = (CPlugin *)(from->appId());
|
|
|
|
// Read the message version
|
|
sint version = msgin.serialVersion(0);
|
|
|
|
// Called when the server sent a POS message
|
|
uint32 count;
|
|
msgin.serial(count);
|
|
|
|
for (uint i = 0;i < count; i++)
|
|
{
|
|
uint32 id;
|
|
msgin.serial(id);
|
|
|
|
plugin->removeEntity (plugin->_Entites[id]);
|
|
}
|
|
}
|
|
|
|
// ***************************************************************************
|
|
void serverSentInfo (CMessage &msgin, TSockId from, CCallbackNetBase &netbase)
|
|
{
|
|
// Called when the server sent a INFO message
|
|
string text;
|
|
msgin.serial(text);
|
|
printf("%s\n", text.c_str());
|
|
}
|
|
|
|
// ***************************************************************************
|
|
// All messages handled by this server
|
|
#define NB_CB 9
|
|
TCallbackItem CallbackArray[NB_CB] =
|
|
{
|
|
{ "ADD", serverSentAdd },
|
|
{ "RMV", serverSentRemove },
|
|
{ "POS", serverSentPos },
|
|
{ "STR", serverSentString },
|
|
{ "INFO", serverSentInfo },
|
|
{ "MISC_PROP", serverSentMiscProp }, // Misc. properties
|
|
{ "SERVER_PARAMS", serverSentParams },
|
|
{ "AUTHENT_VALID", serverSentAuthentValid },
|
|
{ "AUTHENT_INVALID", serverSentAuthentInvalid },
|
|
};
|
|
|
|
// ***************************************************************************
|
|
|
|
void CPlugin::init(IPluginAccess *pluginAccess)
|
|
{
|
|
AFX_MANAGE_STATE(AfxGetStaticModuleState());
|
|
AfxEnableControlContainer();
|
|
try
|
|
{
|
|
_PluginAccess = pluginAccess;
|
|
|
|
// Add the search paths
|
|
NLMISC::CConfigFile::CVar *path = _PluginAccess->getConfigFile().getVarPtr("ShardMonitorPath");
|
|
if (path)
|
|
{
|
|
for(uint k = 0; k < (uint) path->size(); ++k)
|
|
{
|
|
NLMISC::CPath::addSearchPath(path->asString(k), false, false);
|
|
}
|
|
}
|
|
|
|
// connect to the server here...
|
|
|
|
// open the dialog flag window
|
|
_DialogFlag->init(this);
|
|
_DialogFlag->Create(IDD_DIALOG_FLAGS, CWnd::FromHandle(_PluginAccess->getMainWindow()->m_hWnd));
|
|
_DialogFlag->ShowWindow(TRUE);
|
|
|
|
// Read the host where to connect in the client.cfg file
|
|
_PluginActive=true;
|
|
updateConnectionState();
|
|
|
|
// init the sheets
|
|
std::string sheetIDPath = CPath::lookup("sheet_id.bin", false, false);
|
|
if (sheetIDPath.empty())
|
|
{
|
|
_PluginAccess->errorMessage(NULL, "Couldn't find sheet_id.bin, Please check shard monitor paths in world_editor_plugin.cfg.", "Warning", MB_OK|MB_ICONEXCLAMATION);
|
|
}
|
|
else
|
|
{
|
|
NLMISC::CSheetId::init(false);
|
|
}
|
|
|
|
}
|
|
catch (Exception &e)
|
|
{
|
|
errorMessage (e.what ());
|
|
}
|
|
}
|
|
|
|
// ***************************************************************************
|
|
|
|
void CPlugin::connectDisconnect()
|
|
{
|
|
try
|
|
{
|
|
_DialogFlag->UpdateData();
|
|
_DialogFlag->Download = 0;
|
|
_DialogFlag->UpdateData(FALSE);
|
|
if (!_Client || !_Client->connected())
|
|
{
|
|
// Create the socket
|
|
if (_Client)
|
|
delete _Client;
|
|
_Client = new CCallbackClient;
|
|
|
|
// Init and Connect the client to the server located on port 3333
|
|
_Client->addCallbackArray (CallbackArray, NB_CB);
|
|
_Client->getSockId ()->setAppId ((uint64)this);
|
|
|
|
// connect to the server here...
|
|
|
|
// Erase all the primitives
|
|
uint i;
|
|
for (i=0; i<_Entites.size(); i++)
|
|
{
|
|
removeEntity (_Entites[i]);
|
|
}
|
|
|
|
// Erase string cache
|
|
_PrimitiveToString.clear ();
|
|
_StringToPrimitive.clear ();
|
|
_StringIdToString.clear ();
|
|
|
|
// Read the host where to connect in the client.cfg file
|
|
nlassert (_DialogFlag);
|
|
_DialogFlag->UpdateData (TRUE);
|
|
|
|
_SHost = (const char*)_DialogFlag->ShardCtrl.getCurrString();
|
|
|
|
_DialogFlag->ShardCtrl.onOK();
|
|
|
|
try
|
|
{
|
|
CInetAddress addr(_SHost+":48888");
|
|
_Client->connect(addr);
|
|
}
|
|
catch(ESocket &e)
|
|
{
|
|
errorMessage (e.what ());
|
|
return;
|
|
}
|
|
|
|
if (!_Client->connected())
|
|
{
|
|
errorMessage ("The connection failed");
|
|
return;
|
|
}
|
|
|
|
// todo move the looking window
|
|
{
|
|
/*
|
|
CMessage msg;
|
|
msg.setType("WINDOW");
|
|
float xmin = 0;
|
|
float xmax = 10000;
|
|
float ymin = -10000;
|
|
float ymax = 0;
|
|
msg.serial (xmin);
|
|
msg.serial (ymin);
|
|
msg.serial (xmax);
|
|
msg.serial (ymax);
|
|
_Client->send(msg);
|
|
*/
|
|
_WMin = CVector(+0.0f, -10000.0f, 0.0f);
|
|
_WMax = CVector(+10000.0f, +0.0f, 0.0f);
|
|
}
|
|
|
|
_ConnectState = WaitingServerParams;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
_Client->disconnect();
|
|
if (_Client->connected())
|
|
{
|
|
errorMessage ("The disconnection failed");
|
|
delete _Client;
|
|
_Client = NULL;
|
|
return;
|
|
}
|
|
delete _Client;
|
|
_Client = NULL;
|
|
}
|
|
updateConnectionState();
|
|
}
|
|
catch (Exception &e)
|
|
{
|
|
errorMessage (e.what ());
|
|
delete _Client;
|
|
_Client = NULL;
|
|
}
|
|
}
|
|
|
|
// ***************************************************************************
|
|
|
|
/// The current region has changed.
|
|
void CPlugin::primitiveChanged(const NLLIGO::IPrimitive *root)
|
|
{
|
|
}
|
|
|
|
// ***************************************************************************
|
|
|
|
/// The listener has been moved on the map.
|
|
void CPlugin::positionMoved(const NLMISC::CVector &position)
|
|
{
|
|
}
|
|
|
|
// ***************************************************************************
|
|
|
|
void CPlugin::lostPositionControl()
|
|
{
|
|
}
|
|
|
|
// ***************************************************************************
|
|
|
|
void CPlugin::onIdle()
|
|
{
|
|
AFX_MANAGE_STATE(AfxGetStaticModuleState());
|
|
static bool in = false;
|
|
if (!in)
|
|
{
|
|
in = true;
|
|
|
|
// during the first call, we create a Root primitive to store all players
|
|
if (!m_Initialized)
|
|
{
|
|
m_Initialized = true;
|
|
_Root = _PluginAccess->createRootPluginPrimitive("Shard Monitor");
|
|
|
|
// Set the new name
|
|
_Root->removePropertyByName ("name");
|
|
_Root->addPropertyByName ("name", new CPropertyString ("Shard Monitor"));
|
|
|
|
// Invalidate the name
|
|
_PluginAccess->invalidatePluginPrimitive (_Root, LogicTreeParam);
|
|
}
|
|
else
|
|
{
|
|
if (_Client && _Client->connected())
|
|
{
|
|
_ConnectionMsg.MsgType = CConnectionMsg::NoMsg;
|
|
// first, we receive the stack of messages, which is composed of players informations
|
|
_Client->update();
|
|
// See if there's aconnection relarted msg
|
|
switch(_ConnectionMsg.MsgType)
|
|
{
|
|
case CConnectionMsg::ServerParamsMsg:
|
|
serverParamsReceived(_ConnectionMsg.ServerParams);
|
|
break;
|
|
case CConnectionMsg::AuthentValid:
|
|
authentValid();
|
|
break;
|
|
case CConnectionMsg::AuthentInvalid:
|
|
authentInvalid();
|
|
break;
|
|
default:
|
|
// no-op
|
|
break;
|
|
}
|
|
|
|
|
|
if (_ConnectState == Authentificated)
|
|
{
|
|
// here check window coordinates
|
|
CVector vmin, vmax;
|
|
_PluginAccess->getWindowCoordinates(vmin, vmax);
|
|
if (vmin != _WMin || vmax != _WMax)
|
|
{
|
|
CMessage msg;
|
|
msg.setType("WINDOW");
|
|
float xmin = vmin.x;
|
|
float xmax = vmax.x;
|
|
float ymin = vmin.y;
|
|
float ymax = vmax.y;
|
|
msg.serial (xmin);
|
|
msg.serial (ymin);
|
|
msg.serial (xmax);
|
|
msg.serial (ymax);
|
|
_Client->send(msg);
|
|
_WMin = vmin;
|
|
_WMax = vmax;
|
|
}
|
|
|
|
if (_CtrlRequestedDownload != _RequestedDownload)
|
|
{
|
|
_RequestedDownload = _CtrlRequestedDownload;
|
|
CMessage msg;
|
|
msg.setType("BANDW");
|
|
uint32 bandw = _RequestedDownload;
|
|
msg.serial(bandw);
|
|
_Client->send(msg);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update strings
|
|
updateConnectionState();
|
|
|
|
in = false;
|
|
|
|
}
|
|
}
|
|
|
|
const uint HIT_TIME_IN_MS = 1000;
|
|
|
|
// ***************************************************************************
|
|
void CPlugin::displayHits(CDisplay &display)
|
|
{
|
|
std::list<CHit>::iterator it = _HitList.begin();
|
|
while (it != _HitList.end())
|
|
{
|
|
std::list<CHit>::iterator tmpIt = it;
|
|
++ it;
|
|
if (_CurrentTime - tmpIt->HitTime >= HIT_TIME_IN_MS)
|
|
{
|
|
_HitList.erase(tmpIt);
|
|
}
|
|
else
|
|
{
|
|
|
|
CEntityEntry &entity = _Entites[tmpIt->EntityId];
|
|
if (!entity.Hidden && _DisplayHits)
|
|
{
|
|
const CPrimPoint *point = dynamic_cast<const CPrimPoint *>(entity.Primitive);
|
|
if (point)
|
|
{
|
|
// Clip ?
|
|
if (!display.isClipped (&point->Point, 1))
|
|
{
|
|
const float HIT_SIZE = 10.f;
|
|
uint dt = (uint) (_CurrentTime - tmpIt->HitTime);
|
|
float currHitSize = HIT_SIZE * (float) dt / HIT_TIME_IN_MS;
|
|
|
|
// Position in world
|
|
CVector center = point->Point;
|
|
display.worldToPixel (center);
|
|
|
|
// Dot
|
|
CVector corners[] =
|
|
{
|
|
CVector(0.5f, 1.f, 0.f),
|
|
CVector(1.0f, 0.5f, 0.f),
|
|
CVector(1.0f, -0.5f, 0.f),
|
|
CVector(0.5f, -1.f, 0.f),
|
|
CVector(-0.5f, -1.f, 0.f),
|
|
CVector(-1.f, -0.5f, 0.f),
|
|
CVector(-1.f, 0.5f, 0.f),
|
|
CVector(-0.5f, 1.f, 0.f),
|
|
};
|
|
const uint numCorners = sizeofarray(corners);
|
|
for(uint k = 0; k < numCorners; ++k)
|
|
{
|
|
corners[k] = currHitSize * corners[k] + center;
|
|
display.pixelToWorld(corners[k]);
|
|
}
|
|
const CRGBA HIT_COLOR(255, 0, 0);
|
|
for(uint k = 0; k < numCorners; ++k)
|
|
{
|
|
display.lineRenderProxy(HIT_COLOR, corners[k], corners[(k + 1) % numCorners], 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
void CPlugin::pushIcon(CDisplay &display, sint stepX, sint stepY, NLMISC::CVector &currPos, const CEntityIcon &icon, const CPrimTexture &pt)
|
|
{
|
|
// if icon is NULL then no-op
|
|
if (icon.X < 0 || icon.Y < 0) return;
|
|
CVector center = currPos;
|
|
display.worldToPixel(center);
|
|
center.x += (float) stepX;
|
|
center.y += (float) stepY;
|
|
currPos = center;
|
|
display.pixelToWorld(currPos);
|
|
CVector tl(center.x - ENTITY_ICON_SIZE / 2, center.y + ENTITY_ICON_SIZE / 2, 0.f);
|
|
CVector br(center.x + ENTITY_ICON_SIZE / 2, center.y - ENTITY_ICON_SIZE / 2, 0.f);
|
|
display.pixelToWorld(tl);
|
|
display.pixelToWorld(br);
|
|
NLMISC::CQuadColorUV quvc;
|
|
quvc.V0.set(tl.x, tl.y, 0.f);
|
|
quvc.V1.set(br.x, tl.y, 0.f);
|
|
quvc.V2.set(br.x, br.y, 0.f);
|
|
quvc.V3.set(tl.x, br.y, 0.f);
|
|
quvc.Color0 = quvc.Color1 = quvc.Color2 = quvc.Color3 = CRGBA::White;
|
|
if (pt.getWidth() == 0 || pt.getHeight() == 0)
|
|
{
|
|
// if texture width is 0, then texture hasn't been found, so display the whole 'not found' texture
|
|
quvc.Uv0.set(0.f, 0.f);
|
|
quvc.Uv1.set(1.f, 0.f);
|
|
quvc.Uv2.set(1.f, 1.f);
|
|
quvc.Uv3.set(0.f, 1.f);
|
|
}
|
|
else
|
|
{
|
|
float invWidth = 1.f / pt.getWidth();
|
|
float invHeight = 1.f / pt.getHeight();
|
|
sint srcX = icon.X * ENTITY_ICON_SIZE;
|
|
sint srcY = icon.Y * ENTITY_ICON_SIZE;
|
|
quvc.Uv0.set(srcX * invWidth, srcY * invHeight);
|
|
quvc.Uv1.set((srcX + ENTITY_ICON_SIZE) * invWidth, srcY * invHeight);
|
|
quvc.Uv2.set((srcX + ENTITY_ICON_SIZE) * invWidth, (srcY + ENTITY_ICON_SIZE) * invHeight);
|
|
quvc.Uv3.set(srcX * invWidth, (srcY + ENTITY_ICON_SIZE) * invHeight);
|
|
}
|
|
display.texQuadRenderProxy(quvc, 0);
|
|
}
|
|
|
|
// ***************************************************************************
|
|
void CPlugin::displayCloseUp(CDisplay &display)
|
|
{
|
|
// init icon texture if not already done
|
|
if (!_EntityIcons)
|
|
{
|
|
static bool createFailed = false;
|
|
if (createFailed) return;
|
|
HRSRC rsc = FindResource(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDR_ENTITY_ICONS_TGA), "TGA");
|
|
if (rsc == NULL)
|
|
{
|
|
createFailed = true;
|
|
return;
|
|
}
|
|
NLMISC::CBitmap bm;
|
|
if (!_PluginAccess->buildNLBitmapFromTGARsc(rsc, AfxGetInstanceHandle(), bm))
|
|
{
|
|
createFailed = true;
|
|
return;
|
|
}
|
|
_EntityIcons = _PluginAccess->createTexture();
|
|
nlassert(_EntityIcons);
|
|
_EntityIcons->buildFromNLBitmap(bm);
|
|
}
|
|
display.flush(); // must flush before we assign a next texture
|
|
display.setLayerTexture(0, _EntityIcons);
|
|
for(uint k = 0; k < _Entites.size(); ++k)
|
|
{
|
|
if (!_Entites[k].Hidden)
|
|
{
|
|
CEntityEntry &entity = _Entites[k];
|
|
const CPrimPoint *point = dynamic_cast<const CPrimPoint *>(entity.Primitive);
|
|
if (point)
|
|
{
|
|
// Clip ?
|
|
if (!display.isClipped (&point->Point, 1))
|
|
{
|
|
CVector currPos = point->Point;
|
|
// display type of entity
|
|
if (_CloseUpFlags[CloseUpEntityType]) // want to display the type as an icon ?
|
|
{
|
|
// see if there's an icon associated with current type
|
|
pushIcon(display, ENTITY_ICON_SIZE / 2 + 4, 0, currPos, entity.IconForEntityType, *_EntityIcons);
|
|
}
|
|
if (_CloseUpFlags[CloseUpEntityMode]) // want to display the type as an icon ?
|
|
{
|
|
// see if there's an icon associated with current type
|
|
pushIcon(display, ENTITY_ICON_SIZE + 4, 0, currPos, entity.IconForEntityMode, *_EntityIcons);
|
|
}
|
|
if (_CloseUpFlags[CloseUpEntityHP]) // want to display the type as an icon ?
|
|
{
|
|
if (entity.MaxHitPoints != 0)
|
|
{
|
|
uint iconIndex = 0;
|
|
if (entity.HitPoints != 0)
|
|
{
|
|
iconIndex = 1 + (3 * entity.HitPoints) / entity.MaxHitPoints;
|
|
}
|
|
// icons position is hardcoded for now...
|
|
pushIcon(display, ENTITY_ICON_SIZE + 4, 0, currPos, CEntityIcon(iconIndex, 2), *_EntityIcons);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ***************************************************************************
|
|
void CPlugin::postRender(CDisplay &display)
|
|
{
|
|
AFX_MANAGE_STATE(AfxGetStaticModuleState());
|
|
_CurrentTime = NLMISC::CTime::getLocalTime();
|
|
const uint HIT_TIME_IN_MS = 1000;
|
|
// Display entities that are being hit
|
|
displayHits(display);
|
|
// Display close up details
|
|
CVector oneMeter(1.f, 0.f, 0.f);
|
|
display.worldVectorToFloatPixelVector(oneMeter);
|
|
if (oneMeter.x >= _CloseUpDisplayDistance)
|
|
{
|
|
displayCloseUp(display);
|
|
}
|
|
}
|
|
|
|
// ***************************************************************************
|
|
|
|
bool CPlugin::yesNoMessage (const char *format, ... )
|
|
{
|
|
va_list args;
|
|
va_start( args, format );
|
|
char buffer[1024];
|
|
sint ret = vsnprintf( buffer, 1024, format, args );
|
|
va_end( args );
|
|
|
|
return _PluginAccess->yesNoMessage ("Plugin AI : %s", buffer);
|
|
}
|
|
|
|
// ***************************************************************************
|
|
|
|
void CPlugin::errorMessage (const char *format, ... )
|
|
{
|
|
va_list args;
|
|
va_start( args, format );
|
|
char buffer[1024];
|
|
sint ret = vsnprintf( buffer, 1024, format, args );
|
|
va_end( args );
|
|
|
|
_PluginAccess->errorMessage ("Plugin AI : %s", buffer);
|
|
}
|
|
|
|
// ***************************************************************************
|
|
|
|
void CPlugin::infoMessage (const char *format, ... )
|
|
{
|
|
va_list args;
|
|
va_start( args, format );
|
|
char buffer[1024];
|
|
sint ret = vsnprintf( buffer, 1024, format, args );
|
|
va_end( args );
|
|
|
|
_PluginAccess->infoMessage ("Plugin AI : %s", buffer);
|
|
}
|
|
|
|
// ***************************************************************************
|
|
|
|
void CPlugin::updateConnectionState()
|
|
{
|
|
_DialogFlag->UpdateData ();
|
|
_DialogFlag->Sent = "";
|
|
_DialogFlag->Received = "";
|
|
_DialogFlag->DownloadValue = (toString("%.1f kB/s", _DialogFlag->Download/1024.0)).c_str();
|
|
if (!_Client || !_Client->connected())
|
|
{
|
|
_ConnectState = Disconnected;
|
|
// Change the text
|
|
_DialogFlag->ConnectCtrl.SetWindowText (getStringRsc(IDS_CONNECT));
|
|
_DialogFlag->State = getStringRsc(IDS_NOT_CONNECTED);
|
|
}
|
|
else
|
|
{
|
|
switch(_ConnectState)
|
|
{
|
|
case Authentificated:
|
|
{
|
|
static deque< pair<TTime, uint64> > dataSize;
|
|
dataSize.push_back( make_pair(NLMISC::CTime::getLocalTime(), _Client->getBytesReceived()) );
|
|
if (dataSize.size() > 20)
|
|
dataSize.pop_front();
|
|
|
|
double bandwidth = 0.0;
|
|
if (dataSize.size() > 1)
|
|
{
|
|
uint dSize = (uint)(dataSize.back().second - dataSize.front().second);
|
|
uint dt = (uint)(dataSize.back().first - dataSize.front().first);
|
|
bandwidth = (double)dSize*1000.0 / dt;
|
|
}
|
|
|
|
// Change the text
|
|
_DialogFlag->ConnectCtrl.SetWindowText (getStringRsc(IDS_DISCONNECT));
|
|
_DialogFlag->State = (LPCTSTR) (getStringRsc(IDS_CONNECTED_TO) + _SHost.c_str());
|
|
_DialogFlag->Sent = (toString ((uint32)_Client->getBytesSent ()) + (LPCTSTR) getStringRsc(IDS_BYTE_SENT)).c_str();
|
|
//_DialogFlag->Received = (toString ((uint32)_Client->getBytesReceived ()) + " bytes received").c_str();
|
|
_DialogFlag->Received = (toString("%.1f", bandwidth/1024.0) + " kB/s").c_str();
|
|
_DialogFlag->DownloadValue = (toString("%.1f kB/s", _DialogFlag->Download/1024.0)).c_str();
|
|
_CtrlRequestedDownload = _DialogFlag->Download;
|
|
}
|
|
break;
|
|
case WaitingServerParams:
|
|
_DialogFlag->ConnectCtrl.SetWindowText (getStringRsc(IDS_CANCEL_CONNECT));
|
|
_DialogFlag->State = (LPCTSTR) (getStringRsc(IDS_WAITING_SERVER_PARAMS) + _SHost.c_str());
|
|
break;
|
|
case WaitingLoginConfirmation:
|
|
{
|
|
_DialogFlag->ConnectCtrl.SetWindowText (getStringRsc(IDS_CANCEL_CONNECT));
|
|
std::string label = (LPCTSTR) getStringRsc(IDS_WAITING_LOGIN_CONFIRMATION) +_SHost;
|
|
switch((NLMISC::CTime::getLocalTime() / 200) % 4)
|
|
{
|
|
case 0: label+= "-"; break;
|
|
case 1: label+= "/"; break;
|
|
case 2: label+= "|"; break;
|
|
case 3: label+= "\\"; break;
|
|
}
|
|
_DialogFlag->State = label.c_str();
|
|
}
|
|
break;
|
|
default:
|
|
nlassert(0);
|
|
break;
|
|
}
|
|
}
|
|
_DialogFlag->UpdateData (FALSE);
|
|
}
|
|
|
|
// ***************************************************************************
|
|
|
|
void CPlugin::setPrimitiveName (NLLIGO::IPrimitive *primitive, uint32 stringId)
|
|
{
|
|
std::map<uint32, std::string>::iterator ite = _StringIdToString.find (stringId);
|
|
if (ite != _StringIdToString.end ())
|
|
{
|
|
// Set the new name
|
|
primitive->removePropertyByName ("name");
|
|
primitive->addPropertyByName ("name", new CPropertyString (ite->second.c_str()));
|
|
}
|
|
else
|
|
{
|
|
// Set the new name
|
|
primitive->removePropertyByName ("name");
|
|
primitive->addPropertyByName ("name", new CPropertyString ("<unknown>"));
|
|
}
|
|
|
|
// Invalidate the name
|
|
_PluginAccess->invalidatePluginPrimitive (primitive, LogicTreeParam);
|
|
}
|
|
|
|
// ***************************************************************************
|
|
bool CPlugin::isActive()
|
|
{
|
|
return _PluginActive;
|
|
}
|
|
|
|
// ***************************************************************************
|
|
string& CPlugin::getName()
|
|
{
|
|
static string ret="Shard Monitor";
|
|
return ret;
|
|
}
|
|
|
|
// ***************************************************************************
|
|
bool CPlugin::activatePlugin()
|
|
{
|
|
if(!_PluginActive)
|
|
{
|
|
_DialogFlag->ShowWindow(TRUE);
|
|
_PluginActive=true;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// ***************************************************************************
|
|
bool CPlugin::closePlugin()
|
|
{
|
|
if(_PluginActive)
|
|
{
|
|
_DialogFlag->ShowWindow(FALSE);
|
|
|
|
_PluginActive=false;
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// ***************************************************************************
|
|
void CPlugin::setCloseUpFlag(TCloseUpFlag flag, bool on)
|
|
{
|
|
if (on == _CloseUpFlags[flag]) return;
|
|
nlassert(flag < CloseUpFlagCount);
|
|
_CloseUpFlags[flag] = on;
|
|
updateDisplay();
|
|
}
|
|
|
|
CString getStringRsc(UINT strID)
|
|
{
|
|
CString s;
|
|
s.LoadString(strID);
|
|
return s;
|
|
}
|