Store whether a player is activ in C++

This prevents mods from mutating this value and revealing the map.

Part of this commit is written by @phosit.
This commit is contained in:
elexis 2024-12-30 12:14:34 +01:00 committed by phosit
parent f932b8b9cc
commit c9e76efe7b
12 changed files with 187 additions and 39 deletions

View file

@ -512,6 +512,9 @@ Player.prototype.SetState = function(newState, message)
});
}
Engine.PostMessage(this.entity, won ? MT_PlayerWon : MT_PlayerDefeated,
{ "playerId": this.playerID });
if (message)
Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface).PushNotification({
"type": won ? "won" : "defeat",
@ -519,8 +522,6 @@ Player.prototype.SetState = function(newState, message)
"allies": [this.playerID],
"message": message
});
Engine.PostMessage(this.entity, won ? MT_PlayerWon : MT_PlayerDefeated, { "playerId": this.playerID });
};
Player.prototype.GetFormations = function()

View file

@ -10,18 +10,6 @@ Engine.RegisterMessageType("DisabledTechnologiesChanged");
*/
Engine.RegisterMessageType("DisabledTemplatesChanged");
/**
* Message of the form { "playerID": number }
* sent from Player component when a player is defeated.
*/
Engine.RegisterMessageType("PlayerDefeated");
/**
* Message of the form { "playerID": number }
* sent from Player component when a player has won.
*/
Engine.RegisterMessageType("PlayerWon");
/**
* Message of the form { "to": number, "from": number, "amounts": object }
* sent from Player component whenever a tribute is sent.

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2024 Wildfire Games.
/* Copyright (C) 2025 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -486,6 +486,7 @@ bool CGame::IsGameFinished() const
return false;
}
// This function is implemented so that mods can't change it's result. See ICmpPlayer.cpp
bool CGame::PlayerFinished(player_id_t playerID) const
{
CmpPtr<ICmpPlayerManager> cmpPlayerManager(*m_Simulation2, SYSTEM_ENTITY);
@ -493,5 +494,5 @@ bool CGame::PlayerFinished(player_id_t playerID) const
return false;
CmpPtr<ICmpPlayer> cmpPlayer(*m_Simulation2, cmpPlayerManager->GetPlayerByID(playerID));
return cmpPlayer && cmpPlayer->GetState() != "active";
return cmpPlayer && !cmpPlayer->IsActive();
}

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2023 Wildfire Games.
/* Copyright (C) 2025 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -511,6 +511,38 @@ public:
player_id_t player;
};
/**
* Sent by the Player component when a specific player has won.
*/
class CMessagePlayerWon final : public CMessage
{
public:
DEFAULT_MESSAGE_IMPL(PlayerWon)
CMessagePlayerWon(player_id_t playerId) :
playerId{playerId}
{
}
player_id_t playerId;
};
/**
* Sent by the Player component when an specific player has been defeated.
*/
class CMessagePlayerDefeated final : public CMessage
{
public:
DEFAULT_MESSAGE_IMPL(PlayerDefeated)
CMessagePlayerDefeated(player_id_t playerId) :
playerId{playerId}
{
}
player_id_t playerId;
};
/**
* Sent by aura and tech managers when a value of a certain template's component is changed
*/

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2023 Wildfire Games.
/* Copyright (C) 2025 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -61,6 +61,8 @@ MESSAGE(MinimapPing)
MESSAGE(CinemaPathEnded)
MESSAGE(CinemaQueueEnded)
MESSAGE(PlayerColorChanged)
MESSAGE(PlayerWon)
MESSAGE(PlayerDefeated)
// TemplateManager must come before all other (non-test) components,
// so that it is the first to be (de)serialized

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2022 Wildfire Games.
/* Copyright (C) 2025 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -21,6 +21,7 @@
#include "graphics/Color.h"
#include "maths/FixedVector3D.h"
#include "simulation2/system/Component.h"
#include "simulation2/system/InterfaceScripted.h"
#include "simulation2/scripting/ScriptComponent.h"
@ -30,7 +31,41 @@ END_INTERFACE_WRAPPER(Player)
class CCmpPlayerScripted : public ICmpPlayer
{
public:
DEFAULT_SCRIPT_WRAPPER(PlayerScripted)
DEFAULT_SCRIPT_WRAPPER_BASIC(PlayerScripted)
static void ClassInit(CComponentManager& componentManager)
{
componentManager.SubscribeToMessageType(MT_PlayerWon);
componentManager.SubscribeToMessageType(MT_PlayerDefeated);
}
void Serialize(ISerializer& serialize) final
{
serialize.Bool("isActive", m_IsActive);
m_Script.Serialize(serialize);
}
void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize) final
{
deserialize.Bool("isActive", m_IsActive);
m_Script.Deserialize(paramNode, deserialize, GetEntityId());
}
void HandleMessage(const CMessage& msg, bool global) final
{
const int msgType{msg.GetType()};
// Handle messages that were subscribed to in ClassInit.
if (msgType == MT_PlayerWon || msgType == MT_PlayerDefeated)
{
m_IsActive = false;
if (!m_Script.HasMessageHandler(msg, global))
return;
}
// Handle messages that were subscribed to within the JS implementation of the interface.
m_Script.HandleMessage(msg, global);
}
CColor GetDisplayedColor() override
{
@ -56,6 +91,18 @@ public:
{
return m_Script.Call<std::string>("GetState");
}
bool IsActive() final
{
return m_IsActive;
}
private:
// Serialize this player state variable in C++ so that mods can't manipulate this value in order to
// reveal the map locally.
// Once it's set to `false` it's never set to true again. To prevent mods from temporarily changing
// it.
bool m_IsActive{true};
};
REGISTER_COMPONENT_SCRIPT_WRAPPER(PlayerScripted)

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2022 Wildfire Games.
/* Copyright (C) 2025 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -39,6 +39,9 @@ public:
virtual bool HasStartingCamera() = 0;
virtual std::string GetState() = 0;
// See the cpp file for why this is implemented in C++.
virtual bool IsActive() = 0;
DECLARE_INTERFACE_TYPE(Player)
};

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2023 Wildfire Games.
/* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -541,6 +541,38 @@ CMessage* CMessagePlayerColorChanged::FromJSVal(const ScriptRequest& rq, JS::Han
return new CMessagePlayerColorChanged(player);
}
////////////////////////////////
JS::Value CMessagePlayerWon::ToJSVal(const ScriptRequest& rq) const
{
TOJSVAL_SETUP();
SET_MSG_PROPERTY(playerId);
return JS::ObjectValue(*obj);
}
CMessage* CMessagePlayerWon::FromJSVal(const ScriptRequest& rq, JS::HandleValue val)
{
FROMJSVAL_SETUP();
GET_MSG_PROPERTY(player_id_t, playerId);
return new CMessagePlayerWon(playerId);
}
////////////////////////////////
JS::Value CMessagePlayerDefeated::ToJSVal(const ScriptRequest& rq) const
{
TOJSVAL_SETUP();
SET_MSG_PROPERTY(playerId);
return JS::ObjectValue(*obj);
}
CMessage* CMessagePlayerDefeated::FromJSVal(const ScriptRequest& rq, JS::HandleValue val)
{
FROMJSVAL_SETUP();
GET_MSG_PROPERTY(player_id_t, playerId);
return new CMessagePlayerDefeated(playerId);
}
////////////////////////////////////////////////////////////////
CMessage* CMessageFromJSVal(int mtid, const ScriptRequest& rq, JS::HandleValue val)

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2023 Wildfire Games.
/* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -46,6 +46,13 @@ void CComponentTypeScript::Deinit()
ScriptFunction::CallVoid(rq, m_Instance, "Deinit");
}
bool CComponentTypeScript::HasMessageHandler(const CMessage& msg, const bool global)
{
const ScriptRequest rq(m_ScriptInterface);
return Script::HasProperty(rq, m_Instance, global ? msg.GetScriptGlobalHandlerName() :
msg.GetScriptHandlerName());
}
void CComponentTypeScript::HandleMessage(const CMessage& msg, bool global)
{
ScriptRequest rq(m_ScriptInterface);

View file

@ -41,6 +41,7 @@ public:
void Init(const CParamNode& paramNode, entity_id_t ent);
void Deinit();
bool HasMessageHandler(const CMessage& msg, const bool global);
void HandleMessage(const CMessage& msg, bool global);
void Serialize(ISerializer& serialize);
@ -88,8 +89,7 @@ private:
}
#define DEFAULT_SCRIPT_WRAPPER(cname) \
static void ClassInit(CComponentManager& UNUSED(componentManager)) { } \
#define DEFAULT_SCRIPT_WRAPPER_BASIC(cname) \
static IComponent* Allocate(const ScriptInterface& scriptInterface, JS::HandleValue instance) \
{ \
return new CCmp##cname(scriptInterface, instance); \
@ -111,18 +111,6 @@ private:
{ \
m_Script.Deinit(); \
} \
void HandleMessage(const CMessage& msg, bool global) override \
{ \
m_Script.HandleMessage(msg, global); \
} \
void Serialize(ISerializer& serialize) override \
{ \
m_Script.Serialize(serialize); \
} \
void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize) override \
{ \
m_Script.Deserialize(paramNode, deserialize, GetEntityId()); \
} \
JS::Value GetJSInstance() const override \
{ \
return m_Script.GetInstance(); \
@ -135,4 +123,21 @@ private:
CComponentTypeScript m_Script; \
public:
#define DEFAULT_SCRIPT_WRAPPER(cname) \
static void ClassInit(CComponentManager& UNUSED(componentManager)) { } \
void HandleMessage(const CMessage& msg, bool global) override \
{ \
m_Script.HandleMessage(msg, global); \
} \
void Serialize(ISerializer& serialize) override \
{ \
m_Script.Serialize(serialize); \
} \
void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize) override \
{ \
m_Script.Deserialize(paramNode, deserialize, GetEntityId()); \
} \
DEFAULT_SCRIPT_WRAPPER_BASIC(cname)
#endif // INCLUDED_SCRIPTCOMPONENT

View file

@ -20,6 +20,7 @@
#include "ComponentManager.h"
#include "lib/utf8.h"
#include "ps/algorithm.h"
#include "ps/CLogger.h"
#include "ps/Filesystem.h"
#include "ps/Profile.h"
@ -303,10 +304,17 @@ void CComponentManager::Script_RegisterComponentType_Common(int iid, const std::
return;
}
// If we have already subscribed in classInit, do not subscribe again
if (isGlobal)
SubscribeGloballyToMessageType(mit->second);
{
if (!IsGloballySubscribed(mit->second))
SubscribeGloballyToMessageType(mit->second);
}
else
SubscribeToMessageType(mit->second);
{
if (!IsLocallySubscribed(mit->second))
SubscribeToMessageType(mit->second);
}
}
m_CurrentComponent = CID__Invalid;
@ -571,6 +579,18 @@ void CComponentManager::SubscribeGloballyToMessageType(MessageTypeId mtid)
std::sort(types.begin(), types.end()); // TODO: just sort once at the end of LoadComponents
}
bool CComponentManager::IsLocallySubscribed(MessageTypeId mtid)
{
ENSURE(m_CurrentComponent != CID__Invalid);
return PS::contains(m_LocalMessageSubscriptions[mtid], m_CurrentComponent);
}
bool CComponentManager::IsGloballySubscribed(MessageTypeId mtid)
{
ENSURE(m_CurrentComponent != CID__Invalid);
return PS::contains(m_GlobalMessageSubscriptions[mtid], m_CurrentComponent);
}
void CComponentManager::FlattenDynamicSubscriptions()
{
std::map<MessageTypeId, CDynamicSubscription>::iterator it;

View file

@ -113,6 +113,16 @@ public:
*/
void SubscribeGloballyToMessageType(MessageTypeId mtid);
/**
* Check if the current component type is subscribed to messages of the given type.
*/
bool IsLocallySubscribed(MessageTypeId mtid);
/**
* Check if the current component type is globally subscribed to messages of the given type.
*/
bool IsGloballySubscribed(MessageTypeId mtid);
/**
* Subscribe the given component instance to all messages of the given message type.
* The component's HandleMessage will be called on any BroadcastMessage or PostMessage of