Merge pull request #660 from smallmodel/scriptdelegate

Implement and use delegates
This commit is contained in:
smallmodel 2025-02-02 19:28:14 +01:00 committed by GitHub
commit 147678b0f0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 802 additions and 199 deletions

View file

@ -1843,7 +1843,7 @@ void DM_Manager::BuildTeamInfo_ver6(DM_Team *dmTeam)
for (int i = iNumPlayers; i > 0; i--) {
pTeamPlayer = dmTeam->m_players.ObjectAt(i);
if (pTeamPlayer->IsSubclassOfBot()) {
if (pTeamPlayer->edict->r.svFlags & SVF_BOT) {
continue;
}
@ -1888,7 +1888,7 @@ void DM_Manager::BuildTeamInfo_ver15(DM_Team *dmTeam)
for (int i = iNumPlayers; i > 0; i--) {
pTeamPlayer = dmTeam->m_players.ObjectAt(i);
if (pTeamPlayer->IsSubclassOfBot()) {
if (pTeamPlayer->edict->r.svFlags & SVF_BOT) {
continue;
}
@ -1962,7 +1962,7 @@ void DM_Manager::BuildPlayerTeamInfo(DM_Team *dmTeam, int *iPlayerList, DM_Team
pTeamPlayer->GetNumKills(),
pTeamPlayer->GetNumDeaths(),
G_TimeString(level.svsFloatTime - pTeamPlayer->edict->client->pers.enterTime),
pTeamPlayer->IsSubclassOfBot() ? "bot" : va("%d", pTeamPlayer->client->ps.ping)
(pTeamPlayer->edict->r.svFlags & SVF_BOT) ? "bot" : va("%d", pTeamPlayer->client->ps.ping)
);
} else {
Com_sprintf(
@ -1973,7 +1973,7 @@ void DM_Manager::BuildPlayerTeamInfo(DM_Team *dmTeam, int *iPlayerList, DM_Team
pTeamPlayer->GetNumKills(),
pTeamPlayer->GetNumDeaths(),
G_TimeString(level.svsFloatTime - pTeamPlayer->edict->client->pers.enterTime),
pTeamPlayer->IsSubclassOfBot() ? "bot" : va("%d", pTeamPlayer->client->ps.ping)
(pTeamPlayer->edict->r.svFlags & SVF_BOT) ? "bot" : va("%d", pTeamPlayer->client->ps.ping)
);
}

View file

@ -2673,7 +2673,7 @@ void Entity::DamageEvent(Event *ev)
Vector momentum;
Vector position, direction, normal;
int knockback, damageflags, meansofdeath, location;
Event *event;
Event event;
float m;
EntityPtr This;
@ -2759,67 +2759,79 @@ void Entity::DamageEvent(Event *ev)
if (health <= 0) {
if (attacker) {
event = new Event(EV_GotKill);
event->AddEntity(this);
event->AddInteger(damage);
event->AddEntity(inflictor);
event->AddInteger(meansofdeath);
event->AddInteger(0);
const EntityPtr attackerPtr = attacker;
attacker->ProcessEvent(event);
event = Event(EV_GotKill, 5);
event.AddEntity(this);
event.AddInteger(damage);
event.AddEntity(inflictor);
event.AddInteger(meansofdeath);
event.AddInteger(0);
attackerPtr->ProcessEvent(event);
if (attackerPtr) {
attackerPtr->delegate_gotKill.Execute(event);
}
}
if (!This) {
return;
}
event = new Event(EV_Killed);
event->AddEntity(attacker);
event->AddFloat(damage);
event->AddEntity(inflictor);
event->AddVector(position);
event->AddVector(direction);
event->AddVector(normal);
event->AddInteger(knockback);
event->AddInteger(damageflags);
event->AddInteger(meansofdeath);
event->AddInteger(location);
event = Event(EV_Killed, 10);
event.AddEntity(attacker);
event.AddFloat(damage);
event.AddEntity(inflictor);
event.AddVector(position);
event.AddVector(direction);
event.AddVector(normal);
event.AddInteger(knockback);
event.AddInteger(damageflags);
event.AddInteger(meansofdeath);
event.AddInteger(location);
ProcessEvent(event);
if (!This) {
return;
}
// Notify scripts
Unregister(STRING_DAMAGE);
if (!This) {
return;
}
delegate_killed.Execute(event);
return;
}
event = new Event(EV_Pain);
event->AddEntity(attacker);
event->AddFloat(damage);
event->AddEntity(inflictor);
event->AddVector(position);
event->AddVector(direction);
event->AddVector(normal);
event->AddInteger(knockback);
event->AddInteger(damageflags);
event->AddInteger(meansofdeath);
event->AddInteger(location);
event = Event(EV_Pain, 10);
event.AddEntity(attacker);
event.AddFloat(damage);
event.AddEntity(inflictor);
event.AddVector(position);
event.AddVector(direction);
event.AddVector(normal);
event.AddInteger(knockback);
event.AddInteger(damageflags);
event.AddInteger(meansofdeath);
event.AddInteger(location);
ProcessEvent(event);
if (!This) {
return;
}
// Notify scripts
Unregister(STRING_DAMAGE);
if (!This) {
return;
}
delegate_damage.Execute(event);
}
qboolean Entity::IsTouching(Entity *e1)
{
if (e1->absmin.x > absmax.x) {
return false;

View file

@ -51,6 +51,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#include "script.h"
#include "listener.h"
#include "simpleentity.h"
#include "../qcommon/delegate.h"
// modification flags
#define FLAG_IGNORE 0
@ -296,6 +297,12 @@ public:
//====
#endif
MulticastDelegate<void (const Event& ev)> delegate_damage;
MulticastDelegate<void (const Event& ev)> delegate_killed;
MulticastDelegate<void (const Event& ev)> delegate_gotKill;
public:
Entity();
virtual ~Entity();

View file

@ -157,16 +157,16 @@ Begin spawning a new bot entity
*/
void G_BotBegin(gentity_t *ent)
{
PlayerBot *player;
Player *player;
BotController *controller;
level.spawn_entnum = ent->s.number;
player = new PlayerBot;
player = new Player;
G_ClientBegin(ent, NULL);
controller = botManager.getControllerManager().createController(player);
player->setController(controller);
//player->setController(controller);
}
/*
@ -271,7 +271,7 @@ void G_BotShift(int clientNum)
return;
}
if (!ent->entity->IsSubclassOfBot()) {
if (!botManager.getControllerManager().findController(ent->entity)) {
return;
}
@ -355,7 +355,7 @@ bool G_IsBot(gentity_t *ent)
return false;
}
if (!ent->entity || !ent->entity->IsSubclassOfBot()) {
if (!ent->entity || !botManager.getControllerManager().findController(ent->entity)) {
return false;
}
@ -375,7 +375,7 @@ bool G_IsPlayer(gentity_t *ent)
return false;
}
if (!ent->entity || ent->entity->IsSubclassOfBot()) {
if (!ent->entity || botManager.getControllerManager().findController(ent->entity)) {
return false;
}

View file

@ -827,6 +827,7 @@ void G_BotConnect(int clientNum, qboolean firstTime, const char *userinfo)
ent->client = game.clients + clientNum;
ent->s.number = clientNum;
ent->r.svFlags |= SVF_BOT;
client = ent->client;

View file

@ -58,6 +58,12 @@ const Vector power_color(0.0, 1.0, 0.0);
const Vector acolor(1.0, 1.0, 1.0);
const Vector bcolor(1.0, 0.0, 0.0);
ScriptDelegate Player::scriptDelegate_connected("player_connected", "Sent once when the player connected");
ScriptDelegate Player::scriptDelegate_disconnecting("player_disconnecting", "The player is disconnecting");
ScriptDelegate Player::scriptDelegate_spawned("player_spawned", "The player has spawned");
ScriptDelegate Player::scriptDelegate_damage("player_damage", "The player got hit");
ScriptDelegate Player::scriptDelegate_kill("player_killed", "The player got killed");
//
// mohaas 2.0 and above
//
@ -2264,6 +2270,8 @@ void Player::Init(void)
Event *ev = new Event;
ev->AddEntity(this);
scriptDelegate_connected.Trigger(*ev);
scriptedEvents[SE_CONNECTED].Trigger(ev);
}
@ -3217,6 +3225,7 @@ void Player::Killed(Event *ev)
event->AddInteger(ev->GetInteger(10));
event->AddEntity(this);
scriptDelegate_kill.Trigger(*event);
scriptedEvents[SE_KILL].Trigger(event);
Unregister(STRING_DEATH);
@ -9704,14 +9713,16 @@ void Player::ArmorDamage(Event *ev)
event->AddInteger(ev->GetInteger(10));
event->AddEntity(this);
scriptDelegate_damage.Trigger(*event);
scriptedEvents[SE_DAMAGE].Trigger(event);
}
void Player::Disconnect(void)
{
Event *ev = new Event;
ev->AddListener(this);
scriptDelegate_disconnecting.Trigger(*ev);
scriptedEvents[SE_DISCONNECTED].Trigger(ev);
// if (g_gametype->integer != GT_SINGLE_PLAYER) {
@ -10429,9 +10440,12 @@ void Player::EventStuffText(Event *ev)
Event *event = new Event(EV_Player_StuffText);
event->AddValue(ev->GetValue(1));
PostEvent(event, level.frametime, 0);
} else {
gi.SendServerCommand(edict - g_entities, "stufftext \"%s\"", ev->GetString(1).c_str());
return;
}
gi.SendServerCommand(edict - g_entities, "stufftext \"%s\"", ev->GetString(1).c_str());
delegate_stufftext.Execute(ev->GetString(1));
}
void Player::EventSetVoiceType(Event *ev)
@ -12084,8 +12098,9 @@ bool Player::IsReady(void) const
void Player::Spawned(void)
{
Event *ev = new Event;
ev->AddEntity(this);
scriptDelegate_spawned.Trigger(*ev);
scriptedEvents[SE_SPAWN].Trigger(ev);
}

View file

@ -37,6 +37,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#include "actor.h"
#include "vehicle.h"
#include "dm_manager.h"
#include "scriptdelegate.h"
extern Event EV_Player_EndLevel;
extern Event EV_Player_GiveCheat;
@ -310,6 +311,15 @@ private:
bool m_bShowingHint;
#endif
public:
MulticastDelegate<void (const str& text)> delegate_stufftext;
static ScriptDelegate scriptDelegate_connected;
static ScriptDelegate scriptDelegate_disconnecting;
static ScriptDelegate scriptDelegate_spawned;
static ScriptDelegate scriptDelegate_damage;
static ScriptDelegate scriptDelegate_kill;
public:
int m_iNumObjectives;
int m_iObjectivesCompleted;

View file

@ -76,6 +76,15 @@ BotController::BotController()
m_RunLabel.TrySetScript("global/bot_run.scr");
}
BotController::~BotController()
{
if (controlledEnt) {
controlledEnt->delegate_gotKill.Remove(delegateHandle_gotKill);
controlledEnt->delegate_killed.Remove(delegateHandle_killed);
controlledEnt->delegate_stufftext.Remove(delegateHandle_stufftext);
}
}
BotMovement& BotController::GetMovement()
{
return movement;
@ -1051,7 +1060,7 @@ void BotController::Think()
G_ClientThink(controlledEnt->edict, &ucmd, &eyeinfo);
}
void BotController::Killed(Event *ev)
void BotController::Killed(const Event& ev)
{
Entity *attacker;
@ -1068,7 +1077,7 @@ void BotController::Killed(Event *ev)
m_botEyes.angles[0] = 0;
m_botEyes.angles[1] = 0;
attacker = ev->GetEntity(1);
attacker = ev.GetEntity(1);
if (attacker && rand() % 5 == 0) {
// 1/5 chance to go back to the attacker position
@ -1093,7 +1102,7 @@ void BotController::Killed(Event *ev)
G_ClientUserinfoChanged(controlledEnt->edict, controlledEnt->client->pers.userinfo);
}
void BotController::GotKill(Event *ev)
void BotController::GotKill(const Event& ev)
{
ClearEnemy();
m_iCuriousTime = 0;
@ -1118,9 +1127,9 @@ void BotController::GotKill(Event *ev)
}
}
void BotController::EventStuffText(Event *ev)
void BotController::EventStuffText(const str& text)
{
SendCommand(ev->GetString(1));
SendCommand(text);
}
void BotController::setControlledEntity(Player *player)
@ -1128,6 +1137,10 @@ void BotController::setControlledEntity(Player *player)
controlledEnt = player;
movement.SetControlledEntity(player);
rotation.SetControlledEntity(player);
delegateHandle_gotKill = player->delegate_gotKill.Add(std::bind(&BotController::GotKill, this, std::placeholders::_1));
delegateHandle_killed = player->delegate_killed.Add(std::bind(&BotController::Killed, this, std::placeholders::_1));
delegateHandle_stufftext = player->delegate_stufftext.Add(std::bind(&BotController::EventStuffText, this, std::placeholders::_1));
}
Player *BotController::getControlledEntity() const
@ -1216,42 +1229,3 @@ void BotControllerManager::ThinkControllers()
controller->Think();
}
}
CLASS_DECLARATION(Player, PlayerBot, NULL) {
{&EV_Killed, &PlayerBot::Killed },
{&EV_GotKill, &PlayerBot::GotKill },
{&EV_Player_StuffText, &PlayerBot::EventStuffText},
{NULL, NULL }
};
PlayerBot::PlayerBot()
{
entflags |= ECF_BOT;
controller = NULL;
}
void PlayerBot::setController(BotController *controlledBy)
{
controller = controlledBy;
}
void PlayerBot::Spawned(void)
{
controller->Spawned();
Player::Spawned();
}
void PlayerBot::Killed(Event *ev)
{
Player::Killed(ev);
controller->Killed(ev);
}
void PlayerBot::GotKill(Event *ev)
{
Player::GotKill(ev);
controller->GotKill(ev);
}

View file

@ -160,6 +160,11 @@ private:
// Taunts
int m_iNextTauntTime;
private:
DelegateHandle delegateHandle_gotKill;
DelegateHandle delegateHandle_killed;
DelegateHandle delegateHandle_stufftext;
private:
Weapon* FindWeaponWithAmmo(void);
Weapon* FindMeleeWeapon(void);
@ -209,6 +214,7 @@ public:
CLASS_PROTOTYPE(BotController);
BotController();
~BotController();
static void Init(void);
@ -229,9 +235,9 @@ public:
void Spawned(void);
void Killed(Event *ev);
void GotKill(Event *ev);
void EventStuffText(Event *ev);
void Killed(const Event& ev);
void GotKill(const Event& ev);
void EventStuffText(const str& text);
BotMovement& GetMovement();
@ -282,22 +288,3 @@ private:
};
extern BotManager botManager;
class PlayerBot : public Player
{
public:
CLASS_PROTOTYPE(PlayerBot);
public:
PlayerBot();
void setController(BotController *controlledBy);
void Spawned(void) override;
void Killed(Event *ev) override;
void GotKill(Event *ev);
private:
BotController *controller;
};

View file

@ -0,0 +1,171 @@
/*
===========================================================================
Copyright (C) 2025 the OpenMoHAA team
This file is part of OpenMoHAA source code.
OpenMoHAA source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
OpenMoHAA source code 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with OpenMoHAA source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
#include "scriptdelegate.h"
#include "../script/scriptexception.h"
ScriptDelegate *ScriptDelegate::root = NULL;
ScriptRegisteredDelegate_Script::ScriptRegisteredDelegate_Script(const ScriptThreadLabel& inLabel)
: label(inLabel)
{}
void ScriptRegisteredDelegate_Script::Execute(const Event& ev)
{
Event newev = ev;
label.Execute(NULL, newev);
}
bool ScriptRegisteredDelegate_Script::operator==(const ScriptRegisteredDelegate_Script& registeredDelegate) const
{
return label == registeredDelegate.label;
}
ScriptRegisteredDelegate_CodeMember::ScriptRegisteredDelegate_CodeMember(
Class *inObject, DelegateClassResponse inResponse
)
: object(inObject)
, response(inResponse)
{}
void ScriptRegisteredDelegate_CodeMember::Execute(const Event& ev)
{
if (!object) {
return;
}
(object->*response)(ev);
}
bool ScriptRegisteredDelegate_CodeMember::operator==(const ScriptRegisteredDelegate_CodeMember& registeredDelegate
) const
{
return object == registeredDelegate.object && response == registeredDelegate.response;
}
ScriptRegisteredDelegate_Code::ScriptRegisteredDelegate_Code(DelegateResponse inResponse)
: response(inResponse)
{}
void ScriptRegisteredDelegate_Code::Execute(const Event& ev)
{
(*response)(ev);
}
bool ScriptRegisteredDelegate_Code::operator==(const ScriptRegisteredDelegate_Code& registeredDelegate) const
{
return response == registeredDelegate.response;
}
ScriptDelegate::ScriptDelegate(const char *inName, const char *inDescription)
: name(inName)
, description(inDescription)
{
LL_SafeAddFirst(root, this, next, prev);
}
ScriptDelegate::~ScriptDelegate()
{
LL_SafeRemoveRoot(root, this, next, prev);
}
const ScriptDelegate *ScriptDelegate::GetRoot()
{
return root;
}
const ScriptDelegate *ScriptDelegate::GetNext() const
{
return next;
}
void ScriptDelegate::Register(const ScriptThreadLabel& label)
{
if (!label.IsSet()) {
ScriptError("Invalid label specified for the script delegate");
}
list_script.AddUniqueObject(label);
}
void ScriptDelegate::Unregister(const ScriptThreadLabel& label)
{
list_script.RemoveObject(label);
}
void ScriptDelegate::Register(ScriptRegisteredDelegate_Code::DelegateResponse response)
{
list_code.AddUniqueObject(ScriptRegisteredDelegate_Code(response));
}
void ScriptDelegate::Unregister(ScriptRegisteredDelegate_Code::DelegateResponse response)
{
list_code.RemoveObject(response);
}
void ScriptDelegate::Register(Class *object, ScriptRegisteredDelegate_CodeMember::DelegateClassResponse response)
{
list_codeMember.AddUniqueObject(ScriptRegisteredDelegate_CodeMember(object, response));
}
void ScriptDelegate::Unregister(Class *object, ScriptRegisteredDelegate_CodeMember::DelegateClassResponse response)
{
list_codeMember.RemoveObject(ScriptRegisteredDelegate_CodeMember(object, response));
}
void ScriptDelegate::Trigger(const Event& ev) const
{
size_t i;
{
const Container<ScriptRegisteredDelegate_Script> tmpList = list_script;
for (i = 1; i <= tmpList.NumObjects(); i++) {
tmpList.ObjectAt(i).Execute(ev);
}
}
{
const Container<ScriptRegisteredDelegate_Code> tmpList = list_code;
for (i = 1; i <= tmpList.NumObjects(); i++) {
tmpList.ObjectAt(i).Execute(ev);
}
}
{
const Container<ScriptRegisteredDelegate_CodeMember> tmpList = list_codeMember;
for (i = 1; i <= tmpList.NumObjects(); i++) {
tmpList.ObjectAt(i).Execute(ev);
}
}
}
ScriptDelegate *ScriptDelegate::GetScriptDelegate(const char *name)
{
for (ScriptDelegate *delegate = root; delegate; delegate = delegate->next) {
if (!Q_stricmp(delegate->name, name)) {
return delegate;
}
}
return NULL;
}

183
code/fgame/scriptdelegate.h Normal file
View file

@ -0,0 +1,183 @@
/*
===========================================================================
Copyright (C) 2025 the OpenMoHAA team
This file is part of OpenMoHAA source code.
OpenMoHAA source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
OpenMoHAA source code 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with OpenMoHAA source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
// scriptdelegate -- manages function delegate
#include "../qcommon/listener.h"
#include "../qcommon/delegate.h"
#include "gamescript.h"
class ScriptRegisteredDelegate
{
public:
void Execute(const Event& ev);
};
/**
* Registered delegate, for scripts.
* It contains a ScriptThreadLabel with the game script and the label to execute.
*/
class ScriptRegisteredDelegate_Script : public ScriptRegisteredDelegate
{
public:
ScriptRegisteredDelegate_Script(const ScriptThreadLabel& inLabel);
void Execute(const Event& ev);
bool operator==(const ScriptRegisteredDelegate_Script& registeredDelegate) const;
private:
ScriptThreadLabel label;
};
/**
* Registered delegate, for code use.
* It contains the function to execute.
*/
class ScriptRegisteredDelegate_Code : public ScriptRegisteredDelegate
{
public:
using DelegateResponse = void (*)(const Event& ev);
public:
ScriptRegisteredDelegate_Code(DelegateResponse inResponse);
void Execute(const Event& ev);
bool operator==(const ScriptRegisteredDelegate_Code& registeredDelegate) const;
private:
DelegateResponse response;
};
/**
* Registered delegate, for code use.
* It contains the object along the member function to execute.
* The function will not be executed if the object is NULL.
*/
class ScriptRegisteredDelegate_CodeMember : public ScriptRegisteredDelegate
{
public:
using DelegateClassResponse = void (Class::*)(const Event& ev);
public:
ScriptRegisteredDelegate_CodeMember(Class *inObject, DelegateClassResponse inResponse);
void Execute(const Event& ev);
bool operator==(const ScriptRegisteredDelegate_CodeMember& registeredDelegate) const;
private:
SafePtr<Class> object;
DelegateClassResponse response;
};
/**
* A script delegate provides a way for code to subscribe for events.
* Scripts and code can register for a delegate and have their function executed
* when the delegate gets triggered.
*/
class ScriptDelegate
{
public:
ScriptDelegate(const char *name, const char *description);
~ScriptDelegate();
static const ScriptDelegate *GetRoot();
const ScriptDelegate *GetNext() const;
/**
* Register a script label.
*
* @param label The label to be executed
*/
void Register(const ScriptThreadLabel& label);
/**
* Unregistered the label.
*
* @param label The label to unregister
*/
void Unregister(const ScriptThreadLabel& label);
/**
* Register a function.
*
* @param response The function to be executed
*/
void Register(ScriptRegisteredDelegate_Code::DelegateResponse response);
/**
* Unregistered the function.
*
* @param response the function to unregister
*/
void Unregister(ScriptRegisteredDelegate_Code::DelegateResponse response);
/**
* Register with an object and a member function.
*
* @param object The object to notify
* @param response The member function of the object to be executed
*/
void Register(Class *object, ScriptRegisteredDelegate_CodeMember::DelegateClassResponse response);
/**
* Unregistered the member function.
*
* @param object The object where the member function is
* @param response The member function to unregister
*/
void Unregister(Class *object, ScriptRegisteredDelegate_CodeMember::DelegateClassResponse response);
/**
* Executes all registered delegates with the specified event.
*
* @param ev Parameter list
*/
void Trigger(const Event& ev) const;
/**
* Search and return the specified script delegate by name.
*
* @param name The name to search for
*/
static ScriptDelegate *GetScriptDelegate(const char *name);
// non-movable and non-copyable
ScriptDelegate(ScriptDelegate&& other) = delete;
ScriptDelegate& operator=(ScriptDelegate&& other) = delete;
ScriptDelegate(const ScriptDelegate& other) = delete;
ScriptDelegate& operator=(const ScriptDelegate& other) = delete;
private:
// Linked-list
ScriptDelegate *next;
ScriptDelegate *prev;
static ScriptDelegate *root;
const char *name;
const char *description;
Container<ScriptRegisteredDelegate_Script> list_script;
Container<ScriptRegisteredDelegate_Code> list_code;
Container<ScriptRegisteredDelegate_CodeMember> list_codeMember;
};

View file

@ -1676,6 +1676,24 @@ Event EV_ScriptThread_UnregisterEv
"Unregisters script callback handler for specified event",
EV_RETURN
);
Event EV_ScriptThread_Event_Subscribe
(
"event_subscribe",
EV_DEFAULT,
"ss",
"eventname script",
"Subscribe to the specified event. The specified script will be executed when the event gets triggered.",
EV_NORMAL
);
Event EV_ScriptThread_Event_Unsubscribe
(
"event_unsubscribe",
EV_DEFAULT,
"ss",
"eventname script",
"Unsubscribe the script from the specified event.",
EV_NORMAL
);
Event EV_ScriptThread_Conprintf
(
"conprintf",
@ -2193,6 +2211,8 @@ CLASS_DECLARATION(Listener, ScriptThread, NULL) {
{&EV_ScriptThread_TypeOf, &ScriptThread::TypeOfVariable },
{&EV_ScriptThread_RegisterEv, &ScriptThread::RegisterEvent },
{&EV_ScriptThread_UnregisterEv, &ScriptThread::UnregisterEvent },
{&EV_ScriptThread_Event_Subscribe, &ScriptThread::SubscribeEvent },
{&EV_ScriptThread_Event_Unsubscribe, &ScriptThread::UnsubscribeEvent },
{&EV_ScriptThread_CancelWaiting, &ScriptThread::CancelWaiting },
{&EV_ScriptThread_GetTime, &ScriptThread::GetTime },
{&EV_ScriptThread_GetTimeZone, &ScriptThread::GetTimeZone },
@ -6855,6 +6875,8 @@ void ScriptThread::RegisterEvent(Event *ev)
char eventname_full[64];
scriptedEvType_t evType;
ScriptDeprecatedAltMethod("event_subscribe");
eventname = ev->GetString(1);
evType = EventNameToType(eventname, eventname_full);
@ -6886,6 +6908,8 @@ void ScriptThread::UnregisterEvent(Event *ev)
int numArgs = 0;
scriptedEvType_t evType;
ScriptDeprecatedAltMethod("event_unsubscribe");
eventname = ev->GetString(1);
evType = EventNameToType(eventname, NULL);
@ -6919,6 +6943,42 @@ void ScriptThread::UnregisterEvent(Event *ev)
ev->AddInteger(0);
}
void ScriptThread::SubscribeEvent(Event *ev)
{
str eventName;
ScriptDelegate *delegate;
ScriptThreadLabel label;
eventName = ev->GetString(1);
delegate = ScriptDelegate::GetScriptDelegate(eventName);
if (!delegate) {
throw ScriptException("Invalid event '%s'", eventName.c_str());
}
label.SetThread(ev->GetValue(2));
delegate->Register(label);
}
void ScriptThread::UnsubscribeEvent(Event *ev)
{
str eventName;
ScriptDelegate* delegate;
ScriptThreadLabel label;
eventName = ev->GetString(1);
delegate = ScriptDelegate::GetScriptDelegate(eventName);
if (!delegate) {
throw ScriptException("Invalid event '%s'", eventName.c_str());
}
label.SetThread(ev->GetValue(2));
delegate->Unregister(label);
}
void ScriptThread::TypeOfVariable(Event *ev)
{
int numArgs = 0;

View file

@ -345,6 +345,9 @@ public:
void UnregisterEvent(Event *ev);
void VisionGetNaked(Event *ev);
void VisionSetNaked(Event *ev);
void SubscribeEvent(Event *ev);
void UnsubscribeEvent(Event *ev);
};
class OSFile : public Listener

View file

@ -1368,7 +1368,7 @@ void Sentient::ArmorDamage(Event *ev)
Vector position;
Vector normal;
Vector direction;
Event *event;
Event event;
int dflags;
int meansofdeath;
int knockback;
@ -1582,50 +1582,56 @@ void Sentient::ArmorDamage(Event *ev)
health = 0;
if (attacker) {
// Added in OPM
event = new Event(EV_GotKill);
event->AddEntity(this);
event->AddInteger(damage);
event->AddEntity(inflictor);
event->AddInteger(meansofdeath);
event->AddInteger(0);
const EntityPtr attackerPtr = attacker;
attacker->ProcessEvent(event);
// Added in OPM
event = Event(EV_GotKill);
event.AddEntity(this);
event.AddInteger(damage);
event.AddEntity(inflictor);
event.AddInteger(meansofdeath);
event.AddInteger(0);
attackerPtr->ProcessEvent(event);
if (attackerPtr) {
attackerPtr->delegate_gotKill.Execute(event);
}
}
event = new Event(EV_Killed, 10);
event->AddEntity(attacker);
event->AddFloat(damage);
event->AddEntity(inflictor);
event->AddVector(position);
event->AddVector(direction);
event->AddVector(normal);
event->AddInteger(knockback);
event->AddInteger(dflags);
event->AddInteger(meansofdeath);
event->AddInteger(location);
event = Event(EV_Killed, 10);
event.AddEntity(attacker);
event.AddFloat(damage);
event.AddEntity(inflictor);
event.AddVector(position);
event.AddVector(direction);
event.AddVector(normal);
event.AddInteger(knockback);
event.AddInteger(dflags);
event.AddInteger(meansofdeath);
event.AddInteger(location);
ProcessEvent(event);
delegate_killed.Execute(event);
}
if (health > 0) {
// Send pain event
event = new Event(EV_Pain, 10);
event->AddEntity(attacker);
event->AddFloat(damage);
event->AddEntity(inflictor);
event->AddVector(position);
event->AddVector(direction);
event->AddVector(normal);
event->AddInteger(knockback);
event->AddInteger(dflags);
event->AddInteger(meansofdeath);
event->AddInteger(location);
event = Event(EV_Pain, 10);
event.AddEntity(attacker);
event.AddFloat(damage);
event.AddEntity(inflictor);
event.AddVector(position);
event.AddVector(direction);
event.AddVector(normal);
event.AddInteger(knockback);
event.AddInteger(dflags);
event.AddInteger(meansofdeath);
event.AddInteger(location);
ProcessEvent(event);
}
return;
delegate_damage.Execute(*ev);
}
qboolean Sentient::CanBlock(int meansofdeath, qboolean full_block)

View file

@ -356,11 +356,6 @@ int SimpleEntity::IsSubclassOfCrateObject(void) const
return (entflags & ECF_CRATEOBJECT);
}
int SimpleEntity::IsSubclassOfBot(void) const
{
return (entflags & ECF_BOT);
}
void SimpleEntity::SetTargetName(str targetname)
{
if (!world) {

View file

@ -91,7 +91,6 @@ public:
int IsSubclassOfVehiclePoint(void) const;
int IsSubclassOfSplinePath(void) const;
int IsSubclassOfCrateObject(void) const;
int IsSubclassOfBot(void) const;
void GetOrigin(Event *ev);
void SetOrigin(Event *ev);

View file

@ -13,6 +13,7 @@ set(SOURCES_SHARED_UBER
"${CMAKE_SOURCE_DIR}/code/qcommon/class.cpp"
"${CMAKE_SOURCE_DIR}/code/qcommon/con_set.cpp"
"${CMAKE_SOURCE_DIR}/code/qcommon/con_timer.cpp"
"${CMAKE_SOURCE_DIR}/code/qcommon/delegate.cpp"
"${CMAKE_SOURCE_DIR}/code/qcommon/lightclass.cpp"
"${CMAKE_SOURCE_DIR}/code/qcommon/listener.cpp"
"${CMAKE_SOURCE_DIR}/code/qcommon/lz77.cpp"

52
code/qcommon/delegate.cpp Normal file
View file

@ -0,0 +1,52 @@
/*
===========================================================================
Copyright (C) 2025 the OpenMoHAA team
This file is part of OpenMoHAA source code.
OpenMoHAA source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
OpenMoHAA source code 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with OpenMoHAA source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
#include "delegate.h"
uint64_t DelegateHandle::currentHandle = 0;
DelegateHandle::DelegateHandle()
: handle(GenerateDelegateID())
{}
bool DelegateHandle::operator==(const DelegateHandle& other) const
{
return handle == other.handle;
}
bool DelegateHandle::operator!=(const DelegateHandle& other) const
{
return handle != other.handle;
}
uint64_t DelegateHandle::GenerateDelegateID()
{
uint64_t handle;
handle = ++currentHandle;
if (handle == 0) {
handle = ++currentHandle;
}
return handle;
}

127
code/qcommon/delegate.h Normal file
View file

@ -0,0 +1,127 @@
/*
===========================================================================
Copyright (C) 2025 the OpenMoHAA team
This file is part of OpenMoHAA source code.
OpenMoHAA source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
OpenMoHAA source code 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with OpenMoHAA source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
#pragma once
#include <functional>
#include "container.h"
template<typename T>
using Delegate = std::function<T>;
struct DelegateHandle {
public:
DelegateHandle();
bool operator==(const DelegateHandle& other) const;
bool operator!=(const DelegateHandle& other) const;
private:
static uint64_t GenerateDelegateID();
static uint64_t currentHandle;
uint64_t handle;
};
template<typename T>
class DelegateMultiElement
{
public:
DelegateMultiElement(Delegate<T>&& inFunction);
template<typename... Args>
void Execute(Args&&...args) const;
DelegateHandle GetHandle() const;
private:
DelegateHandle handle;
Delegate<T> func;
};
template<typename T>
DelegateMultiElement<T>::DelegateMultiElement(Delegate<T>&& inFunction)
: func(inFunction)
{}
template<typename T>
template<typename... Args>
void DelegateMultiElement<T>::Execute(Args&&...args) const
{
func(std::move(args)...);
}
template<typename T>
DelegateHandle DelegateMultiElement<T>::GetHandle() const
{
return handle;
}
template<typename T>
class MulticastDelegate
{
public:
DelegateHandle Add(Delegate<T>&& function);
void Remove(DelegateHandle handle);
template<typename... Args>
void Execute(Args&&...args);
private:
Container<DelegateMultiElement<T>> delegates;
};
template<typename T>
DelegateHandle MulticastDelegate<T>::Add(Delegate<T>&& function)
{
int index = delegates.AddObject(DelegateMultiElement<T>(std::move(function)));
return delegates.ObjectAt(index).GetHandle();
}
template<typename T>
void MulticastDelegate<T>::Remove(DelegateHandle handle)
{
int i;
for (i = delegates.NumObjects(); i > 0; i--) {
const DelegateMultiElement<T>& elem = delegates.ObjectAt(i);
if (elem.GetHandle() == handle) {
delegates.RemoveObjectAt(i);
break;
}
}
}
template<typename T>
template<typename... Args>
void MulticastDelegate<T>::Execute(Args&&...args)
{
size_t i;
for (i = 1; i <= delegates.NumObjects(); i++) {
const DelegateMultiElement<T>& element = delegates.ObjectAt(i);
element.Execute(std::move(args)...);
}
}

View file

@ -1711,7 +1711,7 @@ Event::Event(const Event& ev)
maxDataSize = ev.maxDataSize;
if (dataSize) {
data = new ScriptVariable[dataSize];
data = new ScriptVariable[maxDataSize];
for (int i = 0; i < dataSize; i++) {
data[i] = ev.data[i];
@ -1733,7 +1733,7 @@ Event::Event(const Event& ev, int numArgs)
maxDataSize = ev.maxDataSize;
if (dataSize) {
data = new ScriptVariable[dataSize];
data = new ScriptVariable[maxDataSize];
for (int i = 0; i < dataSize; i++) {
data[i] = ev.data[i];
@ -2117,7 +2117,7 @@ void Event::Clear(void)
CheckPos
=======================
*/
void Event::CheckPos(int pos)
void Event::CheckPos(int pos) const
{
if (pos > NumArgs()) {
ScriptError("Index %d out of range.", pos);
@ -2129,7 +2129,7 @@ void Event::CheckPos(int pos)
GetBoolean
=======================
*/
bool Event::GetBoolean(int pos)
bool Event::GetBoolean(int pos) const
{
ScriptVariable& variable = GetValue(pos);
@ -2143,7 +2143,7 @@ bool Event::GetBoolean(int pos)
GetConstString
=======================
*/
const_str Event::GetConstString(int pos)
const_str Event::GetConstString(int pos) const
{
ScriptVariable& variable = GetValue(pos);
@ -2157,7 +2157,7 @@ const_str Event::GetConstString(int pos)
GetEntity
=======================
*/
Entity *Event::GetEntity(int pos)
Entity *Event::GetEntity(int pos) const
{
ScriptVariable& variable = GetValue(pos);
@ -2169,7 +2169,7 @@ Entity *Event::GetEntity(int pos)
GetFloat
=======================
*/
float Event::GetFloat(int pos)
float Event::GetFloat(int pos) const
{
ScriptVariable& variable = GetValue(pos);
@ -2181,7 +2181,7 @@ float Event::GetFloat(int pos)
GetInteger
=======================
*/
int Event::GetInteger(int pos)
int Event::GetInteger(int pos) const
{
ScriptVariable& variable = GetValue(pos);
@ -2193,7 +2193,7 @@ int Event::GetInteger(int pos)
GetListener
=======================
*/
Listener *Event::GetListener(int pos)
Listener *Event::GetListener(int pos) const
{
ScriptVariable& variable = GetValue(pos);
@ -2207,7 +2207,7 @@ Listener *Event::GetListener(int pos)
GetSimpleEntity
=======================
*/
SimpleEntity *Event::GetSimpleEntity(int pos)
SimpleEntity *Event::GetSimpleEntity(int pos) const
{
ScriptVariable& variable = GetValue(pos);
@ -2221,7 +2221,7 @@ SimpleEntity *Event::GetSimpleEntity(int pos)
GetString
=======================
*/
str Event::GetString(int pos)
str Event::GetString(int pos) const
{
ScriptVariable& variable = GetValue(pos);
return variable.stringValue();
@ -2232,7 +2232,7 @@ str Event::GetString(int pos)
GetToken
=======================
*/
str Event::GetToken(int pos)
str Event::GetToken(int pos) const
{
ScriptVariable& variable = GetValue(pos);
return variable.stringValue();
@ -2243,7 +2243,7 @@ str Event::GetToken(int pos)
GetValue
=======================
*/
ScriptVariable& Event::GetValue(int pos)
ScriptVariable& Event::GetValue(int pos) const
{
if (pos < 0) {
pos = NumArgs() + pos + 1;
@ -2303,7 +2303,7 @@ ScriptVariable& Event::GetValue(void)
GetVector
=======================
*/
Vector Event::GetVector(int pos)
Vector Event::GetVector(int pos) const
{
ScriptVariable& variable = GetValue(pos);
@ -2317,7 +2317,7 @@ Vector Event::GetVector(int pos)
GetPathNode
=======================
*/
PathNode *Event::GetPathNode(int pos)
PathNode *Event::GetPathNode(int pos) const
{
ScriptVariable& variable = GetValue(pos);
@ -2329,7 +2329,7 @@ PathNode *Event::GetPathNode(int pos)
GetWaypoint
=======================
*/
Waypoint *Event::GetWaypoint(int pos)
Waypoint *Event::GetWaypoint(int pos) const
{
ScriptVariable& variable = GetValue(pos);
@ -2343,7 +2343,7 @@ Waypoint *Event::GetWaypoint(int pos)
IsEntityAt
=======================
*/
qboolean Event::IsEntityAt(int pos)
qboolean Event::IsEntityAt(int pos) const
{
CheckPos(pos);
@ -2355,7 +2355,7 @@ qboolean Event::IsEntityAt(int pos)
IsListenerAt
=======================
*/
qboolean Event::IsListenerAt(int pos)
qboolean Event::IsListenerAt(int pos) const
{
CheckPos(pos);
@ -2367,7 +2367,7 @@ qboolean Event::IsListenerAt(int pos)
IsNilAt
=======================
*/
qboolean Event::IsNilAt(int pos)
qboolean Event::IsNilAt(int pos) const
{
CheckPos(pos);
@ -2379,7 +2379,7 @@ qboolean Event::IsNilAt(int pos)
IsNumericAt
=======================
*/
qboolean Event::IsNumericAt(int pos)
qboolean Event::IsNumericAt(int pos) const
{
CheckPos(pos);
@ -2393,7 +2393,7 @@ qboolean Event::IsNumericAt(int pos)
IsSimpleEntityAt
=======================
*/
qboolean Event::IsSimpleEntityAt(int pos)
qboolean Event::IsSimpleEntityAt(int pos) const
{
CheckPos(pos);
@ -2407,7 +2407,7 @@ qboolean Event::IsSimpleEntityAt(int pos)
IsStringAt
=======================
*/
qboolean Event::IsStringAt(int pos)
qboolean Event::IsStringAt(int pos) const
{
CheckPos(pos);
@ -2419,7 +2419,7 @@ qboolean Event::IsStringAt(int pos)
IsVectorAt
=======================
*/
qboolean Event::IsVectorAt(int pos)
qboolean Event::IsVectorAt(int pos) const
{
CheckPos(pos);
@ -2431,7 +2431,7 @@ qboolean Event::IsVectorAt(int pos)
IsFromScript
=======================
*/
qboolean Event::IsFromScript()
qboolean Event::IsFromScript() const
{
return fromScript;
}
@ -2441,7 +2441,7 @@ qboolean Event::IsFromScript()
NumArgs
=======================
*/
int Event::NumArgs()
int Event::NumArgs() const
{
return dataSize;
}

View file

@ -364,45 +364,45 @@ public:
void Clear(void);
void CheckPos(int pos);
void CheckPos(int pos) const;
bool GetBoolean(int pos);
bool GetBoolean(int pos) const;
const_str GetConstString(int pos);
const_str GetConstString(int pos) const;
Entity *GetEntity(int pos);
Entity *GetEntity(int pos) const;
float GetFloat(int pos);
int GetInteger(int pos);
Listener *GetListener(int pos);
float GetFloat(int pos) const;
int GetInteger(int pos) const;
Listener *GetListener(int pos) const;
class PathNode *GetPathNode(int pos);
class PathNode *GetPathNode(int pos) const;
#ifdef WITH_SCRIPT_ENGINE
SimpleEntity *GetSimpleEntity(int pos);
SimpleEntity *GetSimpleEntity(int pos) const;
#endif
str GetString(int pos);
str GetToken(int pos);
ScriptVariable& GetValue(int pos);
str GetString(int pos) const;
str GetToken(int pos) const;
ScriptVariable& GetValue(int pos) const;
ScriptVariable& GetValue(void);
Vector GetVector(int pos);
Vector GetVector(int pos) const;
class Waypoint *GetWaypoint(int pos);
class Waypoint *GetWaypoint(int pos) const;
qboolean IsEntityAt(int pos);
qboolean IsListenerAt(int pos);
qboolean IsNilAt(int pos);
qboolean IsNumericAt(int pos);
qboolean IsEntityAt(int pos) const;
qboolean IsListenerAt(int pos) const;
qboolean IsNilAt(int pos) const;
qboolean IsNumericAt(int pos) const;
#ifdef WITH_SCRIPT_ENGINE
qboolean IsSimpleEntityAt(int pos);
qboolean IsSimpleEntityAt(int pos) const;
#endif
qboolean IsStringAt(int pos);
qboolean IsVectorAt(int pos);
qboolean IsStringAt(int pos) const;
qboolean IsVectorAt(int pos) const;
qboolean IsFromScript(void);
qboolean IsFromScript(void) const;
int NumArgs();
int NumArgs() const;
};
#define NODE_CANCEL 1