diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b41b87cc1..84e1f576d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1211,6 +1211,7 @@ set (PCH_SOURCES scripting/zscript/zcc_compile.cpp scripting/zscript/zcc_parser.cpp sfmt/SFMT.cpp + events.cpp ) enable_precompiled_headers( g_pch.h PCH_SOURCES ) diff --git a/src/actor.h b/src/actor.h index 42689310b..77c7f81a4 100644 --- a/src/actor.h +++ b/src/actor.h @@ -625,6 +625,9 @@ public: virtual void BeginPlay(); // Called immediately after the actor is created void CallBeginPlay(); + // [ZZ] custom postbeginplay (calls E_WorldThingSpawned) + void CallPostBeginPlay() override; + void LevelSpawned(); // Called after BeginPlay if this actor was spawned by the world void HandleSpawnFlags(); // Translates SpawnFlags into in-game flags. diff --git a/src/b_game.cpp b/src/b_game.cpp index b99b97a60..38fbfafcb 100644 --- a/src/b_game.cpp +++ b/src/b_game.cpp @@ -61,6 +61,7 @@ Everything that is changed is marked (maybe commented) with "Added by MC" #include "d_netinf.h" #include "d_player.h" #include "doomerrors.h" +#include "events.h" static FRandom pr_botspawn ("BotSpawn"); @@ -418,6 +419,9 @@ void FCajunMaster::RemoveAllBots (bool fromlist) } } } + // [ZZ] run event hook + E_PlayerDisconnected(i); + // FBehavior::StaticStartTypedScripts (SCRIPT_Disconnect, players[i].mo, true, i, true); ClearPlayer (i, !fromlist); } diff --git a/src/c_console.cpp b/src/c_console.cpp index 42b6034f2..0b1e58287 100644 --- a/src/c_console.cpp +++ b/src/c_console.cpp @@ -69,6 +69,8 @@ #include "c_consolebuffer.h" #include "g_levellocals.h" +FString FStringFormat(VM_ARGS); // extern from thingdef_data.cpp + #include "gi.h" #define LEFTMARGIN 8 diff --git a/src/d_main.cpp b/src/d_main.cpp index 56b602637..d5c6867ac 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -110,6 +110,7 @@ #include "autosegs.h" #include "fragglescript/t_fs.h" #include "g_levellocals.h" +#include "events.h" EXTERN_CVAR(Bool, hud_althud) void DrawHUD(); @@ -286,6 +287,9 @@ void D_ProcessEvents (void) continue; // console ate the event if (M_Responder (ev)) continue; // menu ate the event + // check events + if (E_Responder(ev)) // [ZZ] ZScript ate the event + continue; G_Responder (ev); } } @@ -306,8 +310,7 @@ void D_PostEvent (const event_t *ev) return; } events[eventhead] = *ev; - if (ev->type == EV_Mouse && !paused && menuactive == MENU_Off && ConsoleState != c_down && ConsoleState != c_falling - ) + if (ev->type == EV_Mouse && !paused && menuactive == MENU_Off && ConsoleState != c_down && ConsoleState != c_falling && !E_CheckUiProcessors()) { if (Button_Mlook.bDown || freelook) { @@ -775,6 +778,9 @@ void D_Display () screen->SetBlendingRect(viewwindowx, viewwindowy, viewwindowx + viewwidth, viewwindowy + viewheight); + // [ZZ] execute event hook that we just started the frame + E_RenderFrame(); + // Renderer->RenderView(&players[consoleplayer]); if ((hw2d = screen->Begin2D(viewactive))) @@ -893,6 +899,8 @@ void D_Display () { NetUpdate (); // send out any new accumulation // normal update + // draw ZScript UI stuff + E_RenderOverlay(); C_DrawConsole (hw2d); // draw console M_Drawer (); // menu is drawn even on top of everything FStat::PrintStat (); diff --git a/src/d_net.cpp b/src/d_net.cpp index 97f6be9bb..0985cf41c 100644 --- a/src/d_net.cpp +++ b/src/d_net.cpp @@ -64,6 +64,7 @@ #include "a_keys.h" #include "intermission/intermission.h" #include "g_levellocals.h" +#include "events.h" EXTERN_CVAR (Int, disableautosave) EXTERN_CVAR (Int, autosavecount) @@ -2678,6 +2679,17 @@ void Net_DoCommand (int type, BYTE **stream, int player) G_ChangeLevel(NULL, 0, 0); break; + case DEM_NETEVENT: + { + FString ename = ReadString(stream); + int argn = ReadByte(stream); + int arg[3] = { 0, 0, 0 }; + for (int i = 0; i < argn; i++) + arg[i] = ReadLong(stream); + E_Console(player, ename, arg[0], arg[1], arg[2]); + } + break; + default: I_Error ("Unknown net command: %d", type); break; diff --git a/src/d_protocol.h b/src/d_protocol.h index 2813b14dc..27af1dae2 100644 --- a/src/d_protocol.h +++ b/src/d_protocol.h @@ -159,6 +159,7 @@ enum EDemoCommand DEM_SETSLOTPNUM, // 67 Byte: player number, the rest is the same as DEM_SETSLOT DEM_REMOVE, // 68 DEM_FINISHGAME, // 69 + DEM_NETEVENT // 70 String: Event name, Byte: Arg count; each arg is a 4-byte int }; // The following are implemented by cht_DoCheat in m_cheat.cpp diff --git a/src/dobject.h b/src/dobject.h index 670605093..ce4714010 100644 --- a/src/dobject.h +++ b/src/dobject.h @@ -203,8 +203,9 @@ enum EObjectFlags OF_SerialSuccess = 1 << 9, // For debugging Serialize() calls OF_Sentinel = 1 << 10, // Object is serving as the sentinel in a ring list OF_Transient = 1 << 11, // Object should not be archived (references to it will be nulled on disk) - OF_Released = 1 << 12, // Object was released from the GC system and should not be processed by GC function - OF_Abstract = 1 << 13, // Marks a class that cannot be created with CreateNew + OF_Spawned = 1 << 12, // Thinker was spawned at all (some thinkers get deleted before spawning) + OF_Released = 1 << 13, // Object was released from the GC system and should not be processed by GC function + OF_Abstract = 1 << 14, // Marks a class that cannot be created with CreateNew }; template class TObjPtr; diff --git a/src/dthinker.cpp b/src/dthinker.cpp index 7b63d2635..da68d8388 100644 --- a/src/dthinker.cpp +++ b/src/dthinker.cpp @@ -309,6 +309,7 @@ DEFINE_ACTION_FUNCTION(DThinker, PostBeginPlay) void DThinker::CallPostBeginPlay() { + ObjectFlags |= OF_Spawned; IFVIRTUAL(DThinker, PostBeginPlay) { // Without the type cast this picks the 'void *' assignment... diff --git a/src/dthinker.h b/src/dthinker.h index e0dcb607f..f86eff266 100644 --- a/src/dthinker.h +++ b/src/dthinker.h @@ -71,7 +71,7 @@ public: virtual void Tick (); void CallTick(); virtual void PostBeginPlay (); // Called just before the first tick - void CallPostBeginPlay(); + virtual void CallPostBeginPlay(); // different in actor. virtual void PostSerialize(); size_t PropagateMark(); diff --git a/src/events.cpp b/src/events.cpp new file mode 100755 index 000000000..95a5af587 --- /dev/null +++ b/src/events.cpp @@ -0,0 +1,1117 @@ +#include "events.h" +#include "virtual.h" +#include "r_utility.h" +#include "g_levellocals.h" +#include "gi.h" +#include "v_text.h" +#include "actor.h" +#include "c_dispatch.h" +#include "d_net.h" + +DStaticEventHandler* E_FirstEventHandler = nullptr; +DStaticEventHandler* E_LastEventHandler = nullptr; + +bool E_RegisterHandler(DStaticEventHandler* handler) +{ + if (handler == nullptr || handler->ObjectFlags & OF_EuthanizeMe) + return false; + if (E_CheckHandler(handler)) + return false; + + handler->OnRegister(); + + // link into normal list + // update: link at specific position based on order. + DStaticEventHandler* before = nullptr; + for (DStaticEventHandler* existinghandler = E_FirstEventHandler; existinghandler; existinghandler = existinghandler->next) + { + if (existinghandler->Order > handler->Order) + { + before = existinghandler; + break; + } + } + + // 1. MyHandler2->1: + // E_FirstEventHandler = MyHandler2, E_LastEventHandler = MyHandler2 + // 2. MyHandler3->2: + // E_FirstEventHandler = MyHandler2, E_LastEventHandler = MyHandler3 + + if (before != nullptr) + { + // if before is not null, link it before the existing handler. + // note that before can be first handler, check for this. + handler->next = before; + handler->prev = before->prev; + before->prev = handler; + if (before == E_FirstEventHandler) + E_FirstEventHandler = handler; + } + else + { + // so if before is null, it means add last. + // it can also mean that we have no handlers at all yet. + handler->prev = E_LastEventHandler; + handler->next = nullptr; + if (E_FirstEventHandler == nullptr) + E_FirstEventHandler = handler; + E_LastEventHandler = handler; + if (handler->prev != nullptr) + handler->prev->next = handler; + } + + if (handler->IsStatic()) + { + handler->ObjectFlags |= OF_Fixed; + handler->ObjectFlags |= OF_Transient; + } + + return true; +} + +bool E_UnregisterHandler(DStaticEventHandler* handler) +{ + if (handler == nullptr || handler->ObjectFlags & OF_EuthanizeMe) + return false; + if (!E_CheckHandler(handler)) + return false; + + handler->OnUnregister(); + + // link out of normal list + if (handler->prev) + handler->prev->next = handler->next; + if (handler->next) + handler->next->prev = handler->prev; + if (handler == E_FirstEventHandler) + E_FirstEventHandler = handler->next; + if (handler == E_LastEventHandler) + E_LastEventHandler = handler->prev; + if (handler->IsStatic()) + { + handler->ObjectFlags &= ~(OF_Fixed|OF_Transient); + handler->Destroy(); + } + return true; +} + +bool E_CheckHandler(DStaticEventHandler* handler) +{ + for (DStaticEventHandler* lhandler = E_FirstEventHandler; lhandler; lhandler = lhandler->next) + if (handler == lhandler) return true; + return false; +} + +bool E_IsStaticType(PClass* type) +{ + return (type->IsDescendantOf(RUNTIME_CLASS(DStaticEventHandler)) && // make sure it's from our hierarchy at all. + !type->IsDescendantOf(RUNTIME_CLASS(DEventHandler))); +} + +void E_SerializeEvents(FSerializer& arc) +{ + // todo : stuff + if (arc.BeginArray("eventhandlers")) + { + int numlocalhandlers = 0; + TArray handlers; + if (arc.isReading()) + { + numlocalhandlers = arc.ArraySize(); + // delete all current local handlers, if any + for (DStaticEventHandler* lhandler = E_FirstEventHandler; lhandler; lhandler = lhandler->next) + if (!lhandler->IsStatic()) lhandler->Destroy(); + } + else + { + for (DStaticEventHandler* lhandler = E_FirstEventHandler; lhandler; lhandler = lhandler->next) + { + if (lhandler->IsStatic()) continue; + numlocalhandlers++; + handlers.Push(lhandler); + } + } + + for (int i = 0; i < numlocalhandlers; i++) + { + // serialize the object properly. + if (arc.isReading()) + { + // get object and put it into the array + DStaticEventHandler* lhandler; + arc(nullptr, lhandler); + if (lhandler != nullptr) + handlers.Push(lhandler); + } + else + { + ::Serialize(arc, nullptr, handlers[i], nullptr); + } + } + + if (arc.isReading()) + { + // add all newly deserialized handlers into the list + for (int i = 0; i < numlocalhandlers; i++) + E_RegisterHandler(handlers[i]); + } + + arc.EndArray(); + } +} + +static void E_InitStaticHandler(PClass* type, FString typestring, bool map) +{ + if (type == nullptr) + { + I_Error("Fatal: unknown event handler class %s in MAPINFO!\n", TEXTCOLOR_ESCAPE, typestring.GetChars()); + return; + + } + + if (E_IsStaticType(type) && map) + { + I_Error("Fatal: invalid event handler class %s in MAPINFO!\nMap-specific event handlers cannot be static.\n", typestring.GetChars()); + return; + } + /* + if (!E_IsStaticType(type) && !map) + { + Printf("%cGWarning: invalid event handler class %s in MAPINFO!\nMAPINFO event handlers should inherit Static* directly!\n", TEXTCOLOR_ESCAPE, typestring.GetChars()); + return; + }*/ + + // check if type already exists, don't add twice. + bool typeExists = false; + for (DStaticEventHandler* handler = E_FirstEventHandler; handler; handler = handler->next) + { + if (handler->IsA(type)) + { + typeExists = true; + break; + } + } + + if (typeExists) return; + DStaticEventHandler* handler = (DStaticEventHandler*)type->CreateNew(); + E_RegisterHandler(handler); +} + +void E_InitStaticHandlers(bool map) +{ + if (savegamerestore) + return; + + // just make sure + E_Shutdown(map); + + if (map) // don't initialize map handlers if restoring from savegame. + { + // load non-static handlers from gameinfo + for (unsigned int i = 0; i < gameinfo.EventHandlers.Size(); i++) + { + FString typestring = gameinfo.EventHandlers[i]; + PClass* type = PClass::FindClass(typestring); + if (!type || E_IsStaticType(type)) // don't init the really global stuff here. + continue; + E_InitStaticHandler(type, typestring, false); + } + + for (unsigned int i = 0; i < level.info->EventHandlers.Size(); i++) + { + FString typestring = level.info->EventHandlers[i]; + PClass* type = PClass::FindClass(typestring); + E_InitStaticHandler(type, typestring, true); + } + } + else + { + for (unsigned int i = 0; i < gameinfo.EventHandlers.Size(); i++) + { + FString typestring = gameinfo.EventHandlers[i]; + PClass* type = PClass::FindClass(typestring); + if (!type || !E_IsStaticType(type)) // don't init map-local global stuff here. + continue; + E_InitStaticHandler(type, typestring, false); + } + } +} + +void E_Shutdown(bool map) +{ + // delete handlers. + for (DStaticEventHandler* handler = E_FirstEventHandler; handler; handler = handler->next) + { + if (handler->IsStatic() == !map) + handler->Destroy(); + } +} + +#define DEFINE_EVENT_LOOPER(name) void E_##name() \ +{ \ + for (DStaticEventHandler* handler = E_FirstEventHandler; handler; handler = handler->next) \ + handler->name(); \ +} + +// note for the functions below. +// *Unsafe is executed on EVERY map load/close, including savegame loading, etc. +// There is no point in allowing non-static handlers to receive unsafe event separately, as there is no point in having static handlers receive safe event. +// Because the main point of safe WorldLoaded/Unloading is that it will be preserved in savegames. +void E_WorldLoaded() +{ + for (DStaticEventHandler* handler = E_FirstEventHandler; handler; handler = handler->next) + { + if (handler->IsStatic()) continue; + if (savegamerestore) continue; // don't execute WorldLoaded for handlers loaded from the savegame. + handler->WorldLoaded(); + } +} + +void E_WorldUnloaded() +{ + for (DStaticEventHandler* handler = E_LastEventHandler; handler; handler = handler->prev) + { + if (handler->IsStatic()) continue; + handler->WorldUnloaded(); + } +} + +void E_WorldLoadedUnsafe() +{ + for (DStaticEventHandler* handler = E_FirstEventHandler; handler; handler = handler->next) + { + if (!handler->IsStatic()) continue; + handler->WorldLoaded(); + } +} + +void E_WorldUnloadedUnsafe() +{ + for (DStaticEventHandler* handler = E_LastEventHandler; handler; handler = handler->prev) + { + if (!handler->IsStatic()) continue; + handler->WorldUnloaded(); + } +} + +void E_WorldThingSpawned(AActor* actor) +{ + // don't call anything if actor was destroyed on PostBeginPlay/BeginPlay/whatever. + if (actor->ObjectFlags & OF_EuthanizeMe) + return; + for (DStaticEventHandler* handler = E_FirstEventHandler; handler; handler = handler->next) + handler->WorldThingSpawned(actor); +} + +void E_WorldThingDied(AActor* actor, AActor* inflictor) +{ + // don't call anything if actor was destroyed on PostBeginPlay/BeginPlay/whatever. + if (actor->ObjectFlags & OF_EuthanizeMe) + return; + for (DStaticEventHandler* handler = E_FirstEventHandler; handler; handler = handler->next) + handler->WorldThingDied(actor, inflictor); +} + +void E_WorldThingRevived(AActor* actor) +{ + // don't call anything if actor was destroyed on PostBeginPlay/BeginPlay/whatever. + if (actor->ObjectFlags & OF_EuthanizeMe) + return; + for (DStaticEventHandler* handler = E_FirstEventHandler; handler; handler = handler->next) + handler->WorldThingRevived(actor); +} + +void E_WorldThingDamaged(AActor* actor, AActor* inflictor, AActor* source, int damage, FName mod, int flags, DAngle angle) +{ + // don't call anything if actor was destroyed on PostBeginPlay/BeginPlay/whatever. + if (actor->ObjectFlags & OF_EuthanizeMe) + return; + for (DStaticEventHandler* handler = E_FirstEventHandler; handler; handler = handler->next) + handler->WorldThingDamaged(actor, inflictor, source, damage, mod, flags, angle); +} + +void E_WorldThingDestroyed(AActor* actor) +{ + // don't call anything if actor was destroyed on PostBeginPlay/BeginPlay/whatever. + if (actor->ObjectFlags & OF_EuthanizeMe) + return; + // don't call anything for non-spawned things (i.e. those that were created, but immediately destroyed) + // this is because Destroyed should be reverse of Spawned. we don't want to catch random inventory give failures. + if (!(actor->ObjectFlags & OF_Spawned)) + return; + for (DStaticEventHandler* handler = E_LastEventHandler; handler; handler = handler->prev) + handler->WorldThingDestroyed(actor); +} + +void E_PlayerEntered(int num, bool fromhub) +{ + // this event can happen during savegamerestore. make sure that local handlers don't receive it. + // actually, global handlers don't want it too. + if (savegamerestore && !fromhub) + return; + + for (DStaticEventHandler* handler = E_FirstEventHandler; handler; handler = handler->next) + handler->PlayerEntered(num, fromhub); +} + +void E_PlayerRespawned(int num) +{ + for (DStaticEventHandler* handler = E_FirstEventHandler; handler; handler = handler->next) + handler->PlayerRespawned(num); +} + +void E_PlayerDied(int num) +{ + for (DStaticEventHandler* handler = E_FirstEventHandler; handler; handler = handler->next) + handler->PlayerDied(num); +} + +void E_PlayerDisconnected(int num) +{ + for (DStaticEventHandler* handler = E_LastEventHandler; handler; handler = handler->prev) + handler->PlayerDisconnected(num); +} + +bool E_Responder(event_t* ev) +{ + if (ev->type == EV_GUI_Event) + { + // iterate handlers back to front by order, and give them this event. + for (DStaticEventHandler* handler = E_LastEventHandler; handler; handler = handler->prev) + { + if (handler->IsUiProcessor && handler->UiProcess(ev)) + return true; // event was processed + } + } + else + { + // not sure if we want to handle device changes, but whatevs. + for (DStaticEventHandler* handler = E_LastEventHandler; handler; handler = handler->prev) + { + if (handler->InputProcess(ev)) + return true; // event was processed + } + } + + return false; +} + +void E_Console(int player, FString name, int arg1, int arg2, int arg3) +{ + for (DStaticEventHandler* handler = E_FirstEventHandler; handler; handler = handler->next) + handler->ConsoleProcess(player, name, arg1, arg2, arg3); +} + +bool E_CheckUiProcessors() +{ + for (DStaticEventHandler* handler = E_FirstEventHandler; handler; handler = handler->next) + if (handler->IsUiProcessor) + return true; + + return false; +} + +bool E_CheckRequireMouse() +{ + for (DStaticEventHandler* handler = E_FirstEventHandler; handler; handler = handler->next) + if (handler->IsUiProcessor && handler->RequireMouse) + return true; + + return false; +} + +// normal event loopers (non-special, argument-less) +DEFINE_EVENT_LOOPER(RenderFrame) +DEFINE_EVENT_LOOPER(RenderOverlay) +DEFINE_EVENT_LOOPER(WorldLightning) +DEFINE_EVENT_LOOPER(WorldTick) + +// declarations +IMPLEMENT_CLASS(DStaticEventHandler, false, false); +IMPLEMENT_CLASS(DEventHandler, false, false); +IMPLEMENT_CLASS(DBaseEvent, false, false) +IMPLEMENT_CLASS(DRenderEvent, false, false) +IMPLEMENT_CLASS(DWorldEvent, false, false) +IMPLEMENT_CLASS(DPlayerEvent, false, false) +IMPLEMENT_CLASS(DUiEvent, false, false) +IMPLEMENT_CLASS(DInputEvent, false, false) +IMPLEMENT_CLASS(DConsoleEvent, false, false) + +DEFINE_FIELD_X(StaticEventHandler, DStaticEventHandler, Order); +DEFINE_FIELD_X(StaticEventHandler, DStaticEventHandler, IsUiProcessor); +DEFINE_FIELD_X(StaticEventHandler, DStaticEventHandler, RequireMouse); + +DEFINE_FIELD_X(RenderEvent, DRenderEvent, ViewPos); +DEFINE_FIELD_X(RenderEvent, DRenderEvent, ViewAngle); +DEFINE_FIELD_X(RenderEvent, DRenderEvent, ViewPitch); +DEFINE_FIELD_X(RenderEvent, DRenderEvent, ViewRoll); +DEFINE_FIELD_X(RenderEvent, DRenderEvent, FracTic); +DEFINE_FIELD_X(RenderEvent, DRenderEvent, Camera); + +DEFINE_FIELD_X(WorldEvent, DWorldEvent, IsSaveGame); +DEFINE_FIELD_X(WorldEvent, DWorldEvent, IsReopen); +DEFINE_FIELD_X(WorldEvent, DWorldEvent, Thing); +DEFINE_FIELD_X(WorldEvent, DWorldEvent, Inflictor); +DEFINE_FIELD_X(WorldEvent, DWorldEvent, Damage); +DEFINE_FIELD_X(WorldEvent, DWorldEvent, DamageSource); +DEFINE_FIELD_X(WorldEvent, DWorldEvent, DamageType); +DEFINE_FIELD_X(WorldEvent, DWorldEvent, DamageFlags); +DEFINE_FIELD_X(WorldEvent, DWorldEvent, DamageAngle); + +DEFINE_FIELD_X(PlayerEvent, DPlayerEvent, PlayerNumber); +DEFINE_FIELD_X(PlayerEvent, DPlayerEvent, IsReturn); + +DEFINE_FIELD_X(UiEvent, DUiEvent, Type); +DEFINE_FIELD_X(UiEvent, DUiEvent, KeyString); +DEFINE_FIELD_X(UiEvent, DUiEvent, KeyChar); +DEFINE_FIELD_X(UiEvent, DUiEvent, MouseX); +DEFINE_FIELD_X(UiEvent, DUiEvent, MouseY); +DEFINE_FIELD_X(UiEvent, DUiEvent, IsShift); +DEFINE_FIELD_X(UiEvent, DUiEvent, IsAlt); +DEFINE_FIELD_X(UiEvent, DUiEvent, IsCtrl); + +DEFINE_FIELD_X(InputEvent, DInputEvent, Type); +DEFINE_FIELD_X(InputEvent, DInputEvent, KeyScan); +DEFINE_FIELD_X(InputEvent, DInputEvent, KeyString); +DEFINE_FIELD_X(InputEvent, DInputEvent, KeyChar); +DEFINE_FIELD_X(InputEvent, DInputEvent, MouseX); +DEFINE_FIELD_X(InputEvent, DInputEvent, MouseY); + +DEFINE_FIELD_X(ConsoleEvent, DConsoleEvent, Player) +DEFINE_FIELD_X(ConsoleEvent, DConsoleEvent, Name) +DEFINE_FIELD_X(ConsoleEvent, DConsoleEvent, Args) + +DEFINE_ACTION_FUNCTION(DStaticEventHandler, SetOrder) +{ + PARAM_SELF_PROLOGUE(DStaticEventHandler); + PARAM_INT(order); + + if (E_CheckHandler(self)) + return 0; + + self->Order = order; + return 0; +} + +DEFINE_ACTION_FUNCTION(DEventHandler, Create) +{ + PARAM_PROLOGUE; + PARAM_CLASS(t, DStaticEventHandler); + // check if type inherits dynamic handlers + if (E_IsStaticType(t)) + { + // disallow static types creation with Create() + ACTION_RETURN_OBJECT(nullptr); + } + // generate a new object of this type. + ACTION_RETURN_OBJECT(t->CreateNew()); +} + +DEFINE_ACTION_FUNCTION(DEventHandler, CreateOnce) +{ + PARAM_PROLOGUE; + PARAM_CLASS(t, DStaticEventHandler); + // check if type inherits dynamic handlers + if (E_IsStaticType(t)) + { + // disallow static types creation with Create() + ACTION_RETURN_OBJECT(nullptr); + } + // check if there are already registered handlers of this type. + for (DStaticEventHandler* handler = E_FirstEventHandler; handler; handler = handler->next) + if (handler->GetClass() == t) // check precise class + ACTION_RETURN_OBJECT(handler); + // generate a new object of this type. + ACTION_RETURN_OBJECT(t->CreateNew()); +} + +DEFINE_ACTION_FUNCTION(DEventHandler, Find) +{ + PARAM_PROLOGUE; + PARAM_CLASS(t, DStaticEventHandler); + for (DStaticEventHandler* handler = E_FirstEventHandler; handler; handler = handler->next) + if (handler->GetClass() == t) // check precise class + ACTION_RETURN_OBJECT(handler); + ACTION_RETURN_OBJECT(nullptr); +} + +DEFINE_ACTION_FUNCTION(DEventHandler, Register) +{ + PARAM_PROLOGUE; + PARAM_OBJECT(handler, DStaticEventHandler); + if (handler->IsStatic()) ACTION_RETURN_BOOL(false); + ACTION_RETURN_BOOL(E_RegisterHandler(handler)); +} + +DEFINE_ACTION_FUNCTION(DEventHandler, Unregister) +{ + PARAM_PROLOGUE; + PARAM_OBJECT(handler, DStaticEventHandler); + if (handler->IsStatic()) ACTION_RETURN_BOOL(false); + ACTION_RETURN_BOOL(E_UnregisterHandler(handler)); +} + +// for static +DEFINE_ACTION_FUNCTION(DStaticEventHandler, Create) +{ + PARAM_PROLOGUE; + PARAM_CLASS(t, DStaticEventHandler); + // static handlers can create any type of object. + // generate a new object of this type. + ACTION_RETURN_OBJECT(t->CreateNew()); +} + +DEFINE_ACTION_FUNCTION(DStaticEventHandler, CreateOnce) +{ + PARAM_PROLOGUE; + PARAM_CLASS(t, DStaticEventHandler); + // static handlers can create any type of object. + // check if there are already registered handlers of this type. + for (DStaticEventHandler* handler = E_FirstEventHandler; handler; handler = handler->next) + if (handler->GetClass() == t) // check precise class + ACTION_RETURN_OBJECT(handler); + // generate a new object of this type. + ACTION_RETURN_OBJECT(t->CreateNew()); +} + +// we might later want to change this +DEFINE_ACTION_FUNCTION(DStaticEventHandler, Find) +{ + PARAM_PROLOGUE; + PARAM_CLASS(t, DStaticEventHandler); + for (DStaticEventHandler* handler = E_FirstEventHandler; handler; handler = handler->next) + if (handler->GetClass() == t) // check precise class + ACTION_RETURN_OBJECT(handler); + ACTION_RETURN_OBJECT(nullptr); +} + +DEFINE_ACTION_FUNCTION(DStaticEventHandler, Register) +{ + PARAM_PROLOGUE; + PARAM_OBJECT(handler, DStaticEventHandler); + ACTION_RETURN_BOOL(E_RegisterHandler(handler)); +} + +DEFINE_ACTION_FUNCTION(DStaticEventHandler, Unregister) +{ + PARAM_PROLOGUE; + PARAM_OBJECT(handler, DStaticEventHandler); + ACTION_RETURN_BOOL(E_UnregisterHandler(handler)); +} + +#define DEFINE_EMPTY_HANDLER(cls, funcname) DEFINE_ACTION_FUNCTION(cls, funcname) \ +{ \ + PARAM_SELF_PROLOGUE(cls); \ + return 0; \ +} + +DEFINE_EMPTY_HANDLER(DStaticEventHandler, OnRegister) +DEFINE_EMPTY_HANDLER(DStaticEventHandler, OnUnregister) + +DEFINE_EMPTY_HANDLER(DStaticEventHandler, WorldLoaded) +DEFINE_EMPTY_HANDLER(DStaticEventHandler, WorldUnloaded) +DEFINE_EMPTY_HANDLER(DStaticEventHandler, WorldThingSpawned) +DEFINE_EMPTY_HANDLER(DStaticEventHandler, WorldThingDied) +DEFINE_EMPTY_HANDLER(DStaticEventHandler, WorldThingRevived) +DEFINE_EMPTY_HANDLER(DStaticEventHandler, WorldThingDamaged) +DEFINE_EMPTY_HANDLER(DStaticEventHandler, WorldThingDestroyed) +DEFINE_EMPTY_HANDLER(DStaticEventHandler, WorldLightning) +DEFINE_EMPTY_HANDLER(DStaticEventHandler, WorldTick) + +DEFINE_EMPTY_HANDLER(DStaticEventHandler, RenderFrame) +DEFINE_EMPTY_HANDLER(DStaticEventHandler, RenderOverlay) + +DEFINE_EMPTY_HANDLER(DStaticEventHandler, PlayerEntered) +DEFINE_EMPTY_HANDLER(DStaticEventHandler, PlayerRespawned) +DEFINE_EMPTY_HANDLER(DStaticEventHandler, PlayerDied) +DEFINE_EMPTY_HANDLER(DStaticEventHandler, PlayerDisconnected) + +DEFINE_EMPTY_HANDLER(DStaticEventHandler, UiProcess); +DEFINE_EMPTY_HANDLER(DStaticEventHandler, InputProcess); + +DEFINE_EMPTY_HANDLER(DStaticEventHandler, ConsoleProcess); + +// =========================================== +// +// Event handlers +// +// =========================================== + +void DStaticEventHandler::OnRegister() +{ + IFVIRTUAL(DStaticEventHandler, OnRegister) + { + // don't create excessive DObjects if not going to be processed anyway + if (func == DStaticEventHandler_OnRegister_VMPtr) + return; + VMValue params[1] = { (DStaticEventHandler*)this }; + GlobalVMStack.Call(func, params, 1, nullptr, 0, nullptr); + } +} + +void DStaticEventHandler::OnUnregister() +{ + IFVIRTUAL(DStaticEventHandler, OnUnregister) + { + // don't create excessive DObjects if not going to be processed anyway + if (func == DStaticEventHandler_OnUnregister_VMPtr) + return; + VMValue params[1] = { (DStaticEventHandler*)this }; + GlobalVMStack.Call(func, params, 1, nullptr, 0, nullptr); + } +} + +static DWorldEvent* E_SetupWorldEvent() +{ + static DWorldEvent* e = nullptr; + if (!e) e = (DWorldEvent*)RUNTIME_CLASS(DWorldEvent)->CreateNew(); + e->IsSaveGame = savegamerestore; + e->IsReopen = level.FromSnapshot && !savegamerestore; // each one by itself isnt helpful, but with hub load we have savegamerestore==0 and level.FromSnapshot==1. + e->Thing = nullptr; + e->Inflictor = nullptr; + e->Damage = 0; + e->DamageAngle = 0.0; + e->DamageFlags = 0; + e->DamageSource = 0; + e->DamageType = NAME_None; + return e; +} + +void DStaticEventHandler::WorldLoaded() +{ + IFVIRTUAL(DStaticEventHandler, WorldLoaded) + { + // don't create excessive DObjects if not going to be processed anyway + if (func == DStaticEventHandler_WorldLoaded_VMPtr) + return; + DWorldEvent* e = E_SetupWorldEvent(); + VMValue params[2] = { (DStaticEventHandler*)this, e }; + GlobalVMStack.Call(func, params, 2, nullptr, 0, nullptr); + } +} + +void DStaticEventHandler::WorldUnloaded() +{ + IFVIRTUAL(DStaticEventHandler, WorldUnloaded) + { + // don't create excessive DObjects if not going to be processed anyway + if (func == DStaticEventHandler_WorldUnloaded_VMPtr) + return; + DWorldEvent* e = E_SetupWorldEvent(); + VMValue params[2] = { (DStaticEventHandler*)this, e }; + GlobalVMStack.Call(func, params, 2, nullptr, 0, nullptr); + } +} + +void DStaticEventHandler::WorldThingSpawned(AActor* actor) +{ + IFVIRTUAL(DStaticEventHandler, WorldThingSpawned) + { + // don't create excessive DObjects if not going to be processed anyway + if (func == DStaticEventHandler_WorldThingSpawned_VMPtr) + return; + DWorldEvent* e = E_SetupWorldEvent(); + e->Thing = actor; + VMValue params[2] = { (DStaticEventHandler*)this, e }; + GlobalVMStack.Call(func, params, 2, nullptr, 0, nullptr); + } +} + +void DStaticEventHandler::WorldThingDied(AActor* actor, AActor* inflictor) +{ + IFVIRTUAL(DStaticEventHandler, WorldThingDied) + { + // don't create excessive DObjects if not going to be processed anyway + if (func == DStaticEventHandler_WorldThingDied_VMPtr) + return; + DWorldEvent* e = E_SetupWorldEvent(); + e->Thing = actor; + e->Inflictor = inflictor; + VMValue params[2] = { (DStaticEventHandler*)this, e }; + GlobalVMStack.Call(func, params, 2, nullptr, 0, nullptr); + } +} + +void DStaticEventHandler::WorldThingRevived(AActor* actor) +{ + IFVIRTUAL(DStaticEventHandler, WorldThingRevived) + { + // don't create excessive DObjects if not going to be processed anyway + if (func == DStaticEventHandler_WorldThingRevived_VMPtr) + return; + DWorldEvent* e = E_SetupWorldEvent(); + e->Thing = actor; + VMValue params[2] = { (DStaticEventHandler*)this, e }; + GlobalVMStack.Call(func, params, 2, nullptr, 0, nullptr); + } +} + +void DStaticEventHandler::WorldThingDamaged(AActor* actor, AActor* inflictor, AActor* source, int damage, FName mod, int flags, DAngle angle) +{ + IFVIRTUAL(DStaticEventHandler, WorldThingDamaged) + { + // don't create excessive DObjects if not going to be processed anyway + if (func == DStaticEventHandler_WorldThingDamaged_VMPtr) + return; + DWorldEvent* e = E_SetupWorldEvent(); + e->Thing = actor; + e->Damage = damage; + e->DamageSource = source; + e->DamageType = mod; + e->DamageFlags = flags; + e->DamageAngle = angle; + VMValue params[2] = { (DStaticEventHandler*)this, e }; + GlobalVMStack.Call(func, params, 2, nullptr, 0, nullptr); + } +} + +void DStaticEventHandler::WorldThingDestroyed(AActor* actor) +{ + IFVIRTUAL(DStaticEventHandler, WorldThingDestroyed) + { + // don't create excessive DObjects if not going to be processed anyway + if (func == DStaticEventHandler_WorldThingDestroyed_VMPtr) + return; + DWorldEvent* e = E_SetupWorldEvent(); + e->Thing = actor; + VMValue params[2] = { (DStaticEventHandler*)this, e }; + GlobalVMStack.Call(func, params, 2, nullptr, 0, nullptr); + } +} + +void DStaticEventHandler::WorldLightning() +{ + IFVIRTUAL(DStaticEventHandler, WorldLightning) + { + // don't create excessive DObjects if not going to be processed anyway + if (func == DStaticEventHandler_WorldLightning_VMPtr) + return; + DWorldEvent* e = E_SetupWorldEvent(); + VMValue params[2] = { (DStaticEventHandler*)this, e }; + GlobalVMStack.Call(func, params, 2, nullptr, 0, nullptr); + } +} + +void DStaticEventHandler::WorldTick() +{ + IFVIRTUAL(DStaticEventHandler, WorldTick) + { + // don't create excessive DObjects if not going to be processed anyway + if (func == DStaticEventHandler_WorldTick_VMPtr) + return; + DWorldEvent* e = E_SetupWorldEvent(); + VMValue params[2] = { (DStaticEventHandler*)this, e }; + GlobalVMStack.Call(func, params, 2, nullptr, 0, nullptr); + } +} + +static DRenderEvent* E_SetupRenderEvent() +{ + static DRenderEvent* e = nullptr; + if (!e) e = (DRenderEvent*)RUNTIME_CLASS(DRenderEvent)->CreateNew(); + e->ViewPos = ::ViewPos; + e->ViewAngle = ::ViewAngle; + e->ViewPitch = ::ViewPitch; + e->ViewRoll = ::ViewRoll; + e->FracTic = ::r_TicFracF; + e->Camera = ::camera; + return e; +} + +void DStaticEventHandler::RenderFrame() +{ + IFVIRTUAL(DStaticEventHandler, RenderFrame) + { + // don't create excessive DObjects if not going to be processed anyway + if (func == DStaticEventHandler_RenderFrame_VMPtr) + return; + DRenderEvent* e = E_SetupRenderEvent(); + VMValue params[2] = { (DStaticEventHandler*)this, e }; + GlobalVMStack.Call(func, params, 2, nullptr, 0, nullptr); + } +} + +void DStaticEventHandler::RenderOverlay() +{ + IFVIRTUAL(DStaticEventHandler, RenderOverlay) + { + // don't create excessive DObjects if not going to be processed anyway + if (func == DStaticEventHandler_RenderOverlay_VMPtr) + return; + DRenderEvent* e = E_SetupRenderEvent(); + VMValue params[2] = { (DStaticEventHandler*)this, e }; + GlobalVMStack.Call(func, params, 2, nullptr, 0, nullptr); + } +} + +static DPlayerEvent* E_SetupPlayerEvent() +{ + static DPlayerEvent* e = nullptr; + if (!e) e = (DPlayerEvent*)RUNTIME_CLASS(DPlayerEvent)->CreateNew(); + e->PlayerNumber = -1; + e->IsReturn = false; + return e; +} + +void DStaticEventHandler::PlayerEntered(int num, bool fromhub) +{ + IFVIRTUAL(DStaticEventHandler, PlayerEntered) + { + // don't create excessive DObjects if not going to be processed anyway + if (func == DStaticEventHandler_PlayerEntered_VMPtr) + return; + DPlayerEvent* e = E_SetupPlayerEvent(); + e->IsReturn = fromhub; + e->PlayerNumber = num; + VMValue params[2] = { (DStaticEventHandler*)this, e }; + GlobalVMStack.Call(func, params, 2, nullptr, 0, nullptr); + } +} + +void DStaticEventHandler::PlayerRespawned(int num) +{ + IFVIRTUAL(DStaticEventHandler, PlayerRespawned) + { + // don't create excessive DObjects if not going to be processed anyway + if (func == DStaticEventHandler_PlayerRespawned_VMPtr) + return; + DPlayerEvent* e = E_SetupPlayerEvent(); + e->PlayerNumber = num; + VMValue params[2] = { (DStaticEventHandler*)this, e }; + GlobalVMStack.Call(func, params, 2, nullptr, 0, nullptr); + } +} + +void DStaticEventHandler::PlayerDied(int num) +{ + IFVIRTUAL(DStaticEventHandler, PlayerDied) + { + // don't create excessive DObjects if not going to be processed anyway + if (func == DStaticEventHandler_PlayerDied_VMPtr) + return; + DPlayerEvent* e = E_SetupPlayerEvent(); + e->PlayerNumber = num; + VMValue params[2] = { (DStaticEventHandler*)this, e }; + GlobalVMStack.Call(func, params, 2, nullptr, 0, nullptr); + } +} + +void DStaticEventHandler::PlayerDisconnected(int num) +{ + IFVIRTUAL(DStaticEventHandler, PlayerDisconnected) + { + // don't create excessive DObjects if not going to be processed anyway + if (func == DStaticEventHandler_PlayerDisconnected_VMPtr) + return; + DPlayerEvent* e = E_SetupPlayerEvent(); + e->PlayerNumber = num; + VMValue params[2] = { (DStaticEventHandler*)this, e }; + GlobalVMStack.Call(func, params, 2, nullptr, 0, nullptr); + } +} + +static DUiEvent* E_SetupUiEvent() +{ + static DUiEvent* e = nullptr; + if (!e) e = (DUiEvent*)RUNTIME_CLASS(DUiEvent)->CreateNew(); + e->Type = EV_GUI_None; + e->IsShift = false; + e->IsAlt = false; + e->IsCtrl = false; + e->MouseX = e->MouseY = 0; + e->KeyChar = 0; + e->KeyString = ""; + return e; +} + +bool DStaticEventHandler::UiProcess(event_t* ev) +{ + IFVIRTUAL(DStaticEventHandler, UiProcess) + { + // don't create excessive DObjects if not going to be processed anyway + if (func == DStaticEventHandler_UiProcess_VMPtr) + return false; + DUiEvent* e = E_SetupUiEvent(); + + // + e->Type = (EGUIEvent)ev->subtype; + // we don't want the modders to remember what weird fields mean what for what events. + switch (e->Type) + { + case EV_GUI_None: + break; + case EV_GUI_KeyDown: + case EV_GUI_KeyRepeat: + case EV_GUI_KeyUp: + e->KeyChar = ev->data1; + e->KeyString.Format("%c", e->KeyChar); + e->IsShift = !!(ev->data3 & GKM_SHIFT); + e->IsAlt = !!(ev->data3 & GKM_ALT); + e->IsCtrl = !!(ev->data3 & GKM_CTRL); + break; + case EV_GUI_Char: + e->KeyChar = ev->data1; + e->KeyString.Format("%c", e->KeyChar); + e->IsAlt = !!ev->data2; // only true for Win32, not sure about SDL + break; + default: // mouse event + // note: SDL input doesn't seem to provide these at all + //Printf("Mouse data: %d, %d, %d, %d\n", ev->x, ev->y, ev->data1, ev->data2); + e->MouseX = ev->data1; + e->MouseY = ev->data2; + e->IsShift = !!(ev->data3 & GKM_SHIFT); + e->IsAlt = !!(ev->data3 & GKM_ALT); + e->IsCtrl = !!(ev->data3 & GKM_CTRL); + break; + } + + int processed; + VMReturn results[1] = { &processed }; + VMValue params[2] = { (DStaticEventHandler*)this, e }; + GlobalVMStack.Call(func, params, 2, results, 1, nullptr); + return !!processed; + } + + return false; +} + +static DInputEvent* E_SetupInputEvent() +{ + static DInputEvent* e = nullptr; + if (!e) e = (DInputEvent*)RUNTIME_CLASS(DInputEvent)->CreateNew(); + e->Type = EV_None; + e->KeyScan = 0; + e->KeyChar = 0; + e->KeyString = ""; + e->MouseX = e->MouseY = 0; + return e; +} + +bool DStaticEventHandler::InputProcess(event_t* ev) +{ + IFVIRTUAL(DStaticEventHandler, InputProcess) + { + // don't create excessive DObjects if not going to be processed anyway + if (func == DStaticEventHandler_InputProcess_VMPtr) + return false; + DInputEvent* e = E_SetupInputEvent(); + + // + e->Type = (EGenericEvent)ev->type; + // we don't want the modders to remember what weird fields mean what for what events. + switch (e->Type) + { + case EV_None: + break; + case EV_KeyDown: + case EV_KeyUp: + e->KeyScan = ev->data1; + e->KeyChar = ev->data2; + e->KeyString.Format("%c", e->KeyChar); + break; + case EV_Mouse: + e->MouseX = ev->x; + e->MouseY = ev->y; + break; + default: + break; // EV_DeviceChange = wat? + } + + int processed; + VMReturn results[1] = { &processed }; + VMValue params[2] = { (DStaticEventHandler*)this, e }; + GlobalVMStack.Call(func, params, 2, results, 1, nullptr); + return !!processed; + } + + return false; +} + +static DConsoleEvent* E_SetupConsoleEvent() +{ + static DConsoleEvent* e = nullptr; + if (!e) e = (DConsoleEvent*)RUNTIME_CLASS(DConsoleEvent)->CreateNew(); + e->Player = -1; + e->Name = ""; + for (int i = 0; i < countof(e->Args); i++) + e->Args[i] = 0; + return e; +} + +void DStaticEventHandler::ConsoleProcess(int player, FString name, int arg1, int arg2, int arg3) +{ + IFVIRTUAL(DStaticEventHandler, ConsoleProcess) + { + // don't create excessive DObjects if not going to be processed anyway + if (func == DStaticEventHandler_ConsoleProcess_VMPtr) + return; + DConsoleEvent* e = E_SetupConsoleEvent(); + + // + e->Player = player; + e->Name = name; + e->Args[0] = arg1; + e->Args[1] = arg2; + e->Args[2] = arg3; + + VMValue params[2] = { (DStaticEventHandler*)this, e }; + GlobalVMStack.Call(func, params, 2, nullptr, 0, nullptr); + } +} + +// +void DStaticEventHandler::OnDestroy() +{ + E_UnregisterHandler(this); + Super::OnDestroy(); +} + +// console stuff +// this is kinda like puke, except it distinguishes between local events and playsim events. +CCMD(event) +{ + int argc = argv.argc(); + + if (argc < 2 || argc > 5) + { + Printf("Usage: event [arg1] [arg2] [arg3]\n"); + } + else + { + int arg[3] = { 0, 0, 0 }; + int argn = MIN(argc - 2, countof(arg)); + for (int i = 0; i < argn; i++) + arg[i] = atoi(argv[2 + i]); + // call locally + E_Console(-1, argv[1], arg[0], arg[1], arg[2]); + } +} + +CCMD(netevent) +{ + if (gamestate != GS_LEVEL/* && gamestate != GS_TITLELEVEL*/) // not sure if this should work in title level, but probably not, because this is for actual playing + { + Printf("netevent cannot be used outside of a map.\n"); + return; + } + + int argc = argv.argc(); + + if (argc < 2 || argc > 5) + { + Printf("Usage: netevent [arg1] [arg2] [arg3]\n"); + } + else + { + int arg[3] = { 0, 0, 0 }; + int argn = MIN(argc - 2, countof(arg)); + for (int i = 0; i < argn; i++) + arg[i] = atoi(argv[2 + i]); + // call networked + Net_WriteByte(DEM_NETEVENT); + Net_WriteString(argv[1]); + Net_WriteByte(argn); + for (int i = 0; i < argn; i++) + Net_WriteLong(arg[i]); + } +} \ No newline at end of file diff --git a/src/events.h b/src/events.h new file mode 100755 index 000000000..71c8a27f6 --- /dev/null +++ b/src/events.h @@ -0,0 +1,303 @@ +#ifndef EVENTS_H +#define EVENTS_H + +#include "dobject.h" +#include "serializer.h" +#include "d_event.h" +#include "d_gui.h" + +class DStaticEventHandler; + +// register +bool E_RegisterHandler(DStaticEventHandler* handler); +// unregister +bool E_UnregisterHandler(DStaticEventHandler* handler); +// find +bool E_CheckHandler(DStaticEventHandler* handler); +// check type +bool E_IsStaticType(PClass* type); +// init static handlers +void E_InitStaticHandlers(bool map); +// shutdown handlers +void E_Shutdown(bool map); + +// called right after the map has loaded (approximately same time as OPEN ACS scripts) +void E_WorldLoaded(); +// called when the map is about to unload (approximately same time as UNLOADING ACS scripts) +void E_WorldUnloaded(); +// called right after the map has loaded (every time, UNSAFE VERSION) +void E_WorldLoadedUnsafe(); +// called right before the map is unloaded (every time, UNSAFE VERSION) +void E_WorldUnloadedUnsafe(); +// called around PostBeginPlay of each actor. +void E_WorldThingSpawned(AActor* actor); +// called after AActor::Die of each actor. +void E_WorldThingDied(AActor* actor, AActor* inflictor); +// called after AActor::Revive. +void E_WorldThingRevived(AActor* actor); +// called before P_DamageMobj and before AActor::DamageMobj virtuals. +void E_WorldThingDamaged(AActor* actor, AActor* inflictor, AActor* source, int damage, FName mod, int flags, DAngle angle); +// called before AActor::Destroy of each actor. +void E_WorldThingDestroyed(AActor* actor); +// same as ACS SCRIPT_Lightning +void E_WorldLightning(); +// this executes on every tick, before everything +void E_WorldTick(); +// called on each render frame once. +void E_RenderFrame(); +// called after everything's been rendered, but before console/menus +void E_RenderOverlay(); +// this executes when a player enters the level (once). PlayerEnter+inhub = RETURN +void E_PlayerEntered(int num, bool fromhub); +// this executes when a player respawns. includes resurrect cheat. +void E_PlayerRespawned(int num); +// this executes when a player dies (partially duplicating worldthingdied, but whatever) +void E_PlayerDied(int num); +// this executes when a player leaves the game +void E_PlayerDisconnected(int num); +// this executes on events. +bool E_Responder(event_t* ev); // splits events into InputProcess and UiProcess +// this executes on console/net events. +void E_Console(int player, FString name, int arg1, int arg2, int arg3); + +// check if there is anything that should receive GUI events +bool E_CheckUiProcessors(); +// check if we need native mouse due to UiProcessors +bool E_CheckRequireMouse(); + +// serialization stuff +void E_SerializeEvents(FSerializer& arc); + +// ============================================== +// +// EventHandler - base class +// +// ============================================== + +class DStaticEventHandler : public DObject // make it a part of normal GC process +{ + DECLARE_CLASS(DStaticEventHandler, DObject) +public: + DStaticEventHandler() + { + prev = 0; + next = 0; + Order = 0; + IsUiProcessor = false; + } + + DStaticEventHandler* prev; + DStaticEventHandler* next; + virtual bool IsStatic() { return true; } + + // + int Order; + bool IsUiProcessor; + bool RequireMouse; + + // serialization handler. let's keep it here so that I don't get lost in serialized/not serialized fields + void Serialize(FSerializer& arc) override + { + Super::Serialize(arc); + if (arc.isReading()) + { + Printf("DStaticEventHandler::Serialize: reading object %s\n", GetClass()->TypeName.GetChars()); + } + else + { + Printf("DStaticEventHandler::Serialize: store object %s\n", GetClass()->TypeName.GetChars()); + } + + arc("Order", Order); + arc("IsUiProcessor", IsUiProcessor); + arc("RequireMouse", RequireMouse); + } + + // destroy handler. this unlinks EventHandler from the list automatically. + void OnDestroy() override; + + // + void OnRegister(); // you can set order and IsUi here. + void OnUnregister(); + + // + void WorldLoaded(); + void WorldUnloaded(); + void WorldThingSpawned(AActor*); + void WorldThingDied(AActor*, AActor*); + void WorldThingRevived(AActor*); + void WorldThingDamaged(AActor*, AActor*, AActor*, int, FName, int, DAngle); + void WorldThingDestroyed(AActor*); + void WorldLightning(); + void WorldTick(); + + // + void RenderFrame(); + void RenderOverlay(); + + // + void PlayerEntered(int num, bool fromhub); + void PlayerRespawned(int num); + void PlayerDied(int num); + void PlayerDisconnected(int num); + + // return true if handled. + bool InputProcess(event_t* ev); + bool UiProcess(event_t* ev); + + // + void ConsoleProcess(int player, FString name, int arg1, int arg2, int arg3); +}; +class DEventHandler : public DStaticEventHandler +{ + DECLARE_CLASS(DEventHandler, DStaticEventHandler) // TODO: make sure this does not horribly break anything +public: + bool IsStatic() override { return false; } +}; +extern DStaticEventHandler* E_FirstEventHandler; + +// we cannot call this DEvent because in ZScript, 'event' is a keyword +class DBaseEvent : public DObject +{ + DECLARE_CLASS(DBaseEvent, DObject) +public: + + DBaseEvent() + { + // each type of event is created only once to avoid new/delete hell + // since from what I remember object creation and deletion results in a lot of GC processing + // (and we aren't supposed to pass event objects around anyway) + this->ObjectFlags |= OF_Fixed; + // we don't want to store events into the savegames because they are global. + this->ObjectFlags |= OF_Transient; + } +}; + +class DRenderEvent : public DBaseEvent +{ + DECLARE_CLASS(DRenderEvent, DBaseEvent) +public: + // these are for all render events + DVector3 ViewPos; + DAngle ViewAngle; + DAngle ViewPitch; + DAngle ViewRoll; + double FracTic; // 0..1 value that describes where we are inside the current gametic, render-wise. + AActor* Camera; + + DRenderEvent() + { + FracTic = 0; + Camera = nullptr; + } +}; + +class DWorldEvent : public DBaseEvent +{ + DECLARE_CLASS(DWorldEvent, DBaseEvent) +public: + // for loaded/unloaded + bool IsSaveGame; + bool IsReopen; + // for thingspawned, thingdied, thingdestroyed + AActor* Thing; + // for thingdied + AActor* Inflictor; // can be null + // for damagemobj + int Damage; + AActor* DamageSource; // can be null + FName DamageType; + int DamageFlags; + DAngle DamageAngle; + + DWorldEvent() + { + IsSaveGame = false; + IsReopen = false; + Thing = nullptr; + Inflictor = nullptr; + Damage = 0; + DamageSource = nullptr; + DamageFlags = 0; + } +}; + +class DPlayerEvent : public DBaseEvent +{ + DECLARE_CLASS(DPlayerEvent, DBaseEvent) +public: + // we currently have only one member: player index + // in ZScript, we have global players[] array from which we can + // get both the player itself and player's body, + // so no need to pass it here. + int PlayerNumber; + // we set this to true if level was reopened (RETURN scripts) + bool IsReturn; + + DPlayerEvent() + { + PlayerNumber = -1; + IsReturn = false; + } +}; + +class DUiEvent : public DBaseEvent +{ + DECLARE_CLASS(DUiEvent, DBaseEvent) +public: + // this essentially translates event_t UI events to ZScript. + EGUIEvent Type; + // for keys/chars/whatever + FString KeyString; + int KeyChar; + // for mouse + int MouseX; + int MouseY; + // global (?) + bool IsShift; + bool IsCtrl; + bool IsAlt; + + DUiEvent() + { + Type = EV_GUI_None; + } +}; + +class DInputEvent : public DBaseEvent +{ + DECLARE_CLASS(DInputEvent, DBaseEvent) +public: + // this translates regular event_t events to ZScript (not UI, UI events are sent via DUiEvent and only if requested!) + EGenericEvent Type; + // for keys + int KeyScan; + FString KeyString; + int KeyChar; + // for mouse + int MouseX; + int MouseY; + + DInputEvent() + { + Type = EV_None; + } +}; + +class DConsoleEvent : public DBaseEvent +{ + DECLARE_CLASS(DConsoleEvent, DBaseEvent) +public: + // player that activated this event. note that it's always -1 for non-playsim events (i.e. these not called with netevent) + int Player; + // + FString Name; + int Args[3]; + + DConsoleEvent() + { + Player = -1; + } +}; + +#endif \ No newline at end of file diff --git a/src/g_game.cpp b/src/g_game.cpp index 44c727b72..627d51f04 100644 --- a/src/g_game.cpp +++ b/src/g_game.cpp @@ -91,6 +91,7 @@ #include "g_hub.h" #include "g_levellocals.h" +#include "events.h" static FRandom pr_dmspawn ("DMSpawn"); @@ -1791,6 +1792,8 @@ void G_DoPlayerPop(int playernum) // [RH] Make the player disappear FBehavior::StaticStopMyScripts(players[playernum].mo); + // [ZZ] fire player disconnect hook + E_PlayerDisconnected(playernum); // [RH] Let the scripts know the player left FBehavior::StaticStartTypedScripts(SCRIPT_Disconnect, players[playernum].mo, true, playernum, true); if (players[playernum].mo != NULL) diff --git a/src/g_level.cpp b/src/g_level.cpp index 28d0d64b2..c34a80bb7 100644 --- a/src/g_level.cpp +++ b/src/g_level.cpp @@ -85,6 +85,7 @@ #include "p_spec.h" #include "serializer.h" #include "virtual.h" +#include "events.h" #include "gi.h" @@ -406,6 +407,10 @@ void G_InitNew (const char *mapname, bool bTitleLevel) bool wantFast; int i; + // did we have any level before? + if (level.info != nullptr) + E_WorldUnloadedUnsafe(); + if (!savegamerestore) { G_ClearHubInfo(); @@ -655,6 +660,10 @@ void G_ChangeLevel(const char *levelname, int position, int flags, int nextSkill // [RH] Give scripts a chance to do something unloading = true; FBehavior::StaticStartTypedScripts (SCRIPT_Unloading, NULL, false, 0, true); + // [ZZ] safe world unload + E_WorldUnloaded(); + // [ZZ] unsafe world unload (changemap != map) + E_WorldUnloadedUnsafe(); unloading = false; STAT_ChangeLevel(nextlevel); @@ -1012,6 +1021,7 @@ void G_DoLoadLevel (int position, bool autosave) } level.maptime = 0; + P_SetupLevel (level.MapName, position); AM_LevelInit(); @@ -1053,6 +1063,7 @@ void G_DoLoadLevel (int position, bool autosave) } level.starttime = gametic; + G_UnSnapshotLevel (!savegamerestore); // [RH] Restore the state of the level. G_FinishTravel (); // For each player, if they are viewing through a player, make sure it is themselves. @@ -1064,6 +1075,10 @@ void G_DoLoadLevel (int position, bool autosave) } } StatusBar->AttachToPlayer (&players[consoleplayer]); + // unsafe world load + E_WorldLoadedUnsafe(); + // regular world load (savegames are handled internally) + E_WorldLoaded(); P_DoDeferedScripts (); // [RH] Do script actions that were triggered on another map. if (demoplayback || oldgs == GS_STARTUP || oldgs == GS_TITLELEVEL) @@ -1241,6 +1256,10 @@ void G_FinishTravel () FPlayerStart *start; int pnum; + // + APlayerPawn* pawns[MAXPLAYERS]; + int pawnsnum = 0; + next = it.Next (); while ( (pawn = next) != NULL) { @@ -1332,12 +1351,23 @@ void G_FinishTravel () { pawn->Speed = pawn->GetDefault()->Speed; } - if (level.FromSnapshot) - { - FBehavior::StaticStartTypedScripts (SCRIPT_Return, pawn, true); + // [ZZ] we probably don't want to fire any scripts before all players are in, especially with runNow = true. + pawns[pawnsnum++] = pawn; + } - // [Nash] run REOPEN scripts upon map re-entry - FBehavior::StaticStartTypedScripts(SCRIPT_Reopen, NULL, false); + // [ZZ] fire the reopen hook. + // if level is loaded from snapshot, and we don't have savegamerestore, this means we returned from a hub. + if (level.FromSnapshot) + { + // [Nash] run REOPEN scripts upon map re-entry + FBehavior::StaticStartTypedScripts(SCRIPT_Reopen, NULL, false); + + for (int i = 0; i < pawnsnum; i++) + { + // [ZZ] fire the enter hook. + E_PlayerEntered(pawns[i]->player - players, true); + // + FBehavior::StaticStartTypedScripts(SCRIPT_Return, pawns[i], true); } } diff --git a/src/g_level.h b/src/g_level.h index 1fadcee3a..d59a4fc09 100644 --- a/src/g_level.h +++ b/src/g_level.h @@ -338,6 +338,8 @@ struct level_info_t TArray PrecacheSounds; TArray PrecacheTextures; TArray PrecacheClasses; + + TArray EventHandlers; level_info_t() { diff --git a/src/g_mapinfo.cpp b/src/g_mapinfo.cpp index cbda6568e..6f2636337 100644 --- a/src/g_mapinfo.cpp +++ b/src/g_mapinfo.cpp @@ -52,6 +52,7 @@ #include "version.h" #include "v_text.h" #include "g_levellocals.h" +#include "events.h" TArray wadclusterinfos; TArray wadlevelinfos; @@ -1046,6 +1047,17 @@ DEFINE_MAP_OPTION(PrecacheSounds, true) } while (parse.sc.CheckString(",")); } +DEFINE_MAP_OPTION(EventHandlers, true) +{ + parse.ParseAssign(); + + do + { + parse.sc.MustGetString(); + info->EventHandlers.Push(parse.sc.String); + } while (parse.sc.CheckString(",")); +} + DEFINE_MAP_OPTION(PrecacheTextures, true) { parse.ParseAssign(); diff --git a/src/g_shared/a_lightning.cpp b/src/g_shared/a_lightning.cpp index deeb8405b..a5401f59a 100644 --- a/src/g_shared/a_lightning.cpp +++ b/src/g_shared/a_lightning.cpp @@ -11,6 +11,7 @@ #include "r_state.h" #include "serializer.h" #include "g_levellocals.h" +#include "events.h" static FRandom pr_lightning ("Lightning"); @@ -129,6 +130,9 @@ void DLightningThinker::LightningFlash () level.flags |= LEVEL_SWAPSKIES; // set alternate sky S_Sound (CHAN_AUTO, "world/thunder", 1.0, ATTN_NONE); + // [ZZ] just in case + E_WorldLightning(); + // start LIGHTNING scripts FBehavior::StaticStartTypedScripts (SCRIPT_Lightning, NULL, false); // [RH] Run lightning scripts // Calculate the next lighting flash diff --git a/src/gi.cpp b/src/gi.cpp index 8d8ff9da7..9f6827ab4 100644 --- a/src/gi.cpp +++ b/src/gi.cpp @@ -317,6 +317,8 @@ void FMapInfoParser::ParseGameInfo() GAMEINFOKEY_STRINGARRAY(PrecachedClasses, "precacheclasses", 0, false) GAMEINFOKEY_STRINGARRAY(PrecachedTextures, "precachetextures", 0, false) GAMEINFOKEY_STRINGARRAY(PrecachedSounds, "precachesounds", 0, false) + GAMEINFOKEY_STRINGARRAY(EventHandlers, "addeventhandlers", 0, true) + GAMEINFOKEY_STRINGARRAY(EventHandlers, "eventhandlers", 0, false) GAMEINFOKEY_STRING(PauseSign, "pausesign") GAMEINFOKEY_STRING(quitSound, "quitSound") GAMEINFOKEY_STRING(BorderFlat, "borderFlat") diff --git a/src/gi.h b/src/gi.h index bd5e84363..1bab4e9a7 100644 --- a/src/gi.h +++ b/src/gi.h @@ -124,6 +124,7 @@ struct gameinfo_t TArray PrecachedClasses; TArray PrecachedTextures; TArray PrecachedSounds; + TArray EventHandlers; FString titleMusic; int titleOrder; diff --git a/src/gl/scene/gl_scene.cpp b/src/gl/scene/gl_scene.cpp index 285b7798d..b852e1380 100644 --- a/src/gl/scene/gl_scene.cpp +++ b/src/gl/scene/gl_scene.cpp @@ -44,6 +44,7 @@ #include "gl/gl_functions.h" #include "serializer.h" #include "g_levellocals.h" +#include "events.h" #include "gl/dynlights/gl_lightbuffer.h" #include "gl/system/gl_interface.h" diff --git a/src/gl/scene/gl_sprite.cpp b/src/gl/scene/gl_sprite.cpp index 2b9756873..5e8bdb560 100644 --- a/src/gl/scene/gl_sprite.cpp +++ b/src/gl/scene/gl_sprite.cpp @@ -37,6 +37,7 @@ #include "a_pickups.h" #include "d_player.h" #include "g_levellocals.h" +#include "events.h" #include "gl/system/gl_interface.h" #include "gl/system/gl_framebuffer.h" diff --git a/src/info.cpp b/src/info.cpp index 58c7241ad..90d847fcc 100644 --- a/src/info.cpp +++ b/src/info.cpp @@ -54,6 +54,7 @@ #include "thingdef.h" #include "d_player.h" #include "doomerrors.h" +#include "events.h" extern void LoadActors (); extern void InitBotStuff(); @@ -210,6 +211,9 @@ void PClassActor::StaticInit() ClearStrifeTypes(); LoadActors (); InitBotStuff(); + + // reinit GLOBAL static stuff from gameinfo, once classes are loaded. + E_InitStaticHandlers(false); } //========================================================================== diff --git a/src/m_cheat.cpp b/src/m_cheat.cpp index 15dd90a07..d11fb9574 100644 --- a/src/m_cheat.cpp +++ b/src/m_cheat.cpp @@ -49,6 +49,7 @@ #include "a_morph.h" #include "g_levellocals.h" #include "virtual.h" +#include "events.h" // [RH] Actually handle the cheat. The cheat code in st_stuff.c now just // writes some bytes to the network data stream, and the network code @@ -347,6 +348,12 @@ void cht_DoCheat (player_t *player, int cheat) P_UndoPlayerMorph(player, player); } + // player is now alive. + // fire E_PlayerRespawned and start the ACS SCRIPT_Respawn. + E_PlayerRespawned(player - players); + // + FBehavior::StaticStartTypedScripts(SCRIPT_Respawn, player->mo, true); + } } break; diff --git a/src/p_interaction.cpp b/src/p_interaction.cpp index c33cfb080..24b37a56a 100644 --- a/src/p_interaction.cpp +++ b/src/p_interaction.cpp @@ -60,6 +60,7 @@ #include "a_morph.h" #include "virtual.h" #include "g_levellocals.h" +#include "events.h" static FRandom pr_obituary ("Obituary"); static FRandom pr_botrespawn ("BotRespawn"); @@ -384,6 +385,9 @@ void AActor::Die (AActor *source, AActor *inflictor, int dmgflags) target = source; } + // [ZZ] Fire WorldThingDied script hook. + E_WorldThingDied(this, inflictor); + // [JM] Fire KILL type scripts for actor. Not needed for players, since they have the "DEATH" script type. if (!player && !(flags7 & MF7_NOKILLSCRIPTS) && ((flags7 & MF7_USEKILLSCRIPTS) || gameinfo.forcekillscripts)) { @@ -606,6 +610,9 @@ void AActor::Die (AActor *source, AActor *inflictor, int dmgflags) // [RH] Death messages ClientObituary (this, inflictor, source, dmgflags); + // [ZZ] fire player death hook + E_PlayerDied(player - players); + // Death script execution, care of Skull Tag FBehavior::StaticStartTypedScripts (SCRIPT_Death, this, true); @@ -1593,7 +1600,12 @@ DEFINE_ACTION_FUNCTION(AActor, DamageMobj) PARAM_NAME(mod); PARAM_INT_DEF(flags); PARAM_FLOAT_DEF(angle); - ACTION_RETURN_INT(DamageMobj(self, inflictor, source, damage, mod, flags, angle)); + + // [ZZ] event handlers need the result. + int realdamage = DamageMobj(self, inflictor, source, damage, mod, flags, angle); + if (!realdamage) ACTION_RETURN_INT(0); + E_WorldThingDamaged(self, inflictor, source, realdamage, mod, flags, angle); + ACTION_RETURN_INT(realdamage); } int P_DamageMobj(AActor *target, AActor *inflictor, AActor *source, int damage, FName mod, int flags, DAngle angle) @@ -1607,7 +1619,14 @@ int P_DamageMobj(AActor *target, AActor *inflictor, AActor *source, int damage, GlobalVMStack.Call(func, params, 7, &ret, 1, nullptr); return retval; } - else return DamageMobj(target, inflictor, source, damage, mod, flags, angle); + else + { + int realdamage = DamageMobj(target, inflictor, source, damage, mod, flags, angle); + if (!realdamage) return 0; + // [ZZ] event handlers only need the resultant damage (they can't do anything about it anyway) + E_WorldThingDamaged(target, inflictor, source, realdamage, mod, flags, angle); + return realdamage; + } } diff --git a/src/p_mobj.cpp b/src/p_mobj.cpp index 829674ba6..d879d206d 100644 --- a/src/p_mobj.cpp +++ b/src/p_mobj.cpp @@ -71,6 +71,7 @@ #include "virtual.h" #include "g_levellocals.h" #include "a_morph.h" +#include "events.h" // MACROS ------------------------------------------------------------------ @@ -4973,6 +4974,12 @@ void AActor::PostBeginPlay () flags7 |= MF7_HANDLENODELAY; } +void AActor::CallPostBeginPlay() +{ + Super::CallPostBeginPlay(); + E_WorldThingSpawned(this); +} + void AActor::MarkPrecacheSounds() const { SeeSound.MarkUsed(); @@ -5104,6 +5111,12 @@ void AActor::CallDeactivate(AActor *activator) void AActor::OnDestroy () { + // [ZZ] call destroy event hook. + // note that this differs from ThingSpawned in that you can actually override OnDestroy to avoid calling the hook. + // but you can't really do that without utterly breaking the game, so it's ok. + // note: if OnDestroy is ever made optional, E_WorldThingDestroyed should still be called for ANY thing. + E_WorldThingDestroyed(this); + ClearRenderSectorList(); ClearRenderLineList(); @@ -5416,6 +5429,9 @@ APlayerPawn *P_SpawnPlayer (FPlayerStart *mthing, int playernum, int flags) { if (state == PST_ENTER || (state == PST_LIVE && !savegamerestore)) { + // [ZZ] fire non-hub ENTER event + // level.time is a hack to make sure that we don't call it on dummy player initialization during hub return. + if (!level.time) E_PlayerEntered(p - players, false); FBehavior::StaticStartTypedScripts (SCRIPT_Enter, p->mo, true); } else if (state == PST_REBORN) @@ -7462,6 +7478,9 @@ void AActor::Revive() { level.total_monsters++; } + + // [ZZ] resurrect hook + E_WorldThingRevived(this); } int AActor::GetGibHealth() const diff --git a/src/p_saveg.cpp b/src/p_saveg.cpp index 48908ea71..8f465e081 100644 --- a/src/p_saveg.cpp +++ b/src/p_saveg.cpp @@ -63,6 +63,7 @@ #include "r_renderer.h" #include "serializer.h" #include "g_levellocals.h" +#include "events.h" static TStaticArray loadsectors; static TStaticArray loadlines; @@ -968,6 +969,8 @@ void G_SerializeLevel(FSerializer &arc, bool hubload) arc("sectorportals", level.sectorPortals); if (arc.isReading()) P_CollectLinkedPortals(); + // [ZZ] serialize events + E_SerializeEvents(arc); DThinker::SerializeThinkers(arc, !hubload); arc.Array("polyobjs", polyobjs, po_NumPolyobjs); arc("subsectors", subsectors); diff --git a/src/p_setup.cpp b/src/p_setup.cpp index 0c3b65710..875d3fbc0 100644 --- a/src/p_setup.cpp +++ b/src/p_setup.cpp @@ -78,6 +78,7 @@ #ifndef NO_EDATA #include "edata.h" #endif +#include "events.h" #include "fragglescript/t_fs.h" @@ -3432,6 +3433,8 @@ extern polyblock_t **PolyBlockMap; void P_FreeLevelData () { + // [ZZ] delete per-map event handlers + E_Shutdown(true); MapThingsConverted.Clear(); MapThingsUserDataIndex.Clear(); MapThingsUserData.Clear(); @@ -3637,6 +3640,10 @@ void P_SetupLevel (const char *lumpname, int position) I_Error("Unable to open map '%s'\n", lumpname); } + // [ZZ] init per-map static handlers. we need to call this before everything is set up because otherwise scripts don't receive PlayerEntered event + // (which happens at god-knows-what stage in this function, but definitely not the last part, because otherwise it'd work to put E_InitStaticHandlers before the player spawning) + E_InitStaticHandlers(true); + // generate a checksum for the level, to be included and checked with savegames. map->GetChecksum(level.md5); // find map num @@ -4194,6 +4201,8 @@ void P_Init () static void P_Shutdown () { + // [ZZ] delete global event handlers + E_Shutdown(false); R_DeinitSpriteData (); P_DeinitKeyMessages (); P_FreeLevelData (); diff --git a/src/p_spec.cpp b/src/p_spec.cpp index e5a1178d8..0eb619723 100644 --- a/src/p_spec.cpp +++ b/src/p_spec.cpp @@ -39,6 +39,7 @@ #include "d_event.h" #include "g_level.h" #include "gstrings.h" +#include "events.h" #include "i_system.h" #include "m_argv.h" diff --git a/src/p_tick.cpp b/src/p_tick.cpp index 2f8151425..990cdaf64 100644 --- a/src/p_tick.cpp +++ b/src/p_tick.cpp @@ -36,6 +36,7 @@ #include "r_utility.h" #include "p_spec.h" #include "g_levellocals.h" +#include "events.h" extern gamestate_t wipegamestate; @@ -125,6 +126,8 @@ void P_Ticker (void) /*Added by MC: Freeze mode.*/!(bglobal.freeze && players[i].Bot != NULL)) P_PlayerThink (&players[i]); + // [ZZ] call the WorldTick hook + E_WorldTick(); StatusBar->Tick (); // [RH] moved this here level.Tick (); // [RH] let the level tick DThinker::RunThinkers (); diff --git a/src/posix/cocoa/i_input.mm b/src/posix/cocoa/i_input.mm index 74ef5c1cc..5f4aec801 100644 --- a/src/posix/cocoa/i_input.mm +++ b/src/posix/cocoa/i_input.mm @@ -47,6 +47,7 @@ #include "doomdef.h" #include "doomstat.h" #include "v_video.h" +#include "events.h" #undef Class @@ -94,6 +95,10 @@ void CheckGUICapture() ? (c_down == ConsoleState || c_falling == ConsoleState || chatmodeon) : (MENU_On == menuactive || MENU_OnNoPause == menuactive); + // [ZZ] check active event handlers that want the UI processing + if (!wantCapture && E_CheckUiProcessors()) + wantCapture = true; + if (wantCapture != GUICapture) { GUICapture = wantCapture; @@ -181,6 +186,9 @@ void CheckNativeMouse() && (MENU_On == menuactive || MENU_OnNoPause == menuactive); } + if (!wantNative && E_CheckRequireMouse()) + wantNative = true; + I_SetNativeMouse(wantNative); } diff --git a/src/posix/sdl/i_input.cpp b/src/posix/sdl/i_input.cpp index 372b23446..759da062c 100644 --- a/src/posix/sdl/i_input.cpp +++ b/src/posix/sdl/i_input.cpp @@ -17,6 +17,7 @@ #include "dikeys.h" #include "templates.h" #include "s_sound.h" +#include "events.h" static void I_CheckGUICapture (); static void I_CheckNativeMouse (); @@ -154,6 +155,10 @@ static void I_CheckGUICapture () wantCapt = (menuactive == MENU_On || menuactive == MENU_OnNoPause); } + // [ZZ] check active event handlers that want the UI processing + if (!wantCapt && E_CheckUiProcessors()) + wantCapt = true; + if (wantCapt != GUICapture) { GUICapture = wantCapt; @@ -331,6 +336,12 @@ void MessagePump (const SDL_Event &sev) event.subtype = sev.type == SDL_MOUSEBUTTONDOWN ? EV_GUI_LButtonDown : EV_GUI_LButtonUp; event.subtype += (sev.button.button - 1) * 3; } + + SDL_Keymod kmod = SDL_GetModState(); + event.data3 = ((kmod & KMOD_SHIFT) ? GKM_SHIFT : 0) | + ((kmod & KMOD_CTRL) ? GKM_CTRL : 0) | + ((kmod & KMOD_ALT) ? GKM_ALT : 0); + D_PostEvent(&event); } break; @@ -340,6 +351,10 @@ void MessagePump (const SDL_Event &sev) { event.type = EV_GUI_Event; event.subtype = sev.wheel.y > 0 ? EV_GUI_WheelUp : EV_GUI_WheelDown; + SDL_Keymod kmod = SDL_GetModState(); + event.data3 = ((kmod & KMOD_SHIFT) ? GKM_SHIFT : 0) | + ((kmod & KMOD_CTRL) ? GKM_CTRL : 0) | + ((kmod & KMOD_ALT) ? GKM_ALT : 0); D_PostEvent (&event); } else diff --git a/src/r_main.cpp b/src/r_main.cpp index 7030339c7..4a8e796ad 100644 --- a/src/r_main.cpp +++ b/src/r_main.cpp @@ -59,6 +59,7 @@ #include "r_data/colormaps.h" #include "p_maputl.h" #include "r_thread.h" +#include "events.h" CVAR (String, r_viewsize, "", CVAR_NOSET) CVAR (Bool, r_shadercolormaps, true, CVAR_ARCHIVE) diff --git a/src/r_plane.cpp b/src/r_plane.cpp index 29f852ece..62730e731 100644 --- a/src/r_plane.cpp +++ b/src/r_plane.cpp @@ -59,6 +59,7 @@ #include "v_palette.h" #include "r_data/colormaps.h" #include "g_levellocals.h" +#include "events.h" #ifdef _MSC_VER #pragma warning(disable:4244) diff --git a/src/r_things.cpp b/src/r_things.cpp index b4ee5edbb..76dae9eea 100644 --- a/src/r_things.cpp +++ b/src/r_things.cpp @@ -66,6 +66,7 @@ #include "p_maputl.h" #include "g_levellocals.h" #include "r_thread.h" +#include "events.h" EXTERN_CVAR(Bool, st_scale) EXTERN_CVAR(Bool, r_shadercolormaps) diff --git a/src/scripting/backend/codegen.cpp b/src/scripting/backend/codegen.cpp index d991d1459..db7d7425f 100644 --- a/src/scripting/backend/codegen.cpp +++ b/src/scripting/backend/codegen.cpp @@ -8368,9 +8368,9 @@ FxExpression *FxVMFunctionCall::Resolve(FCompileContext& ctx) if (x) { if (x->ValueType == TypeName || - x->ValueType == TypeSound) + x->ValueType == TypeSound) // spriteID can be a string too. { - x = new FxStringCast(ArgList[i]); + x = new FxStringCast(x); x = x->Resolve(ctx); } } diff --git a/src/version.h b/src/version.h index 8ed9e56aa..e50bdfd8d 100644 --- a/src/version.h +++ b/src/version.h @@ -61,7 +61,7 @@ const char *GetVersionString(); // Protocol version used in demos. // Bump it if you change existing DEM_ commands or add new ones. // Otherwise, it should be safe to leave it alone. -#define DEMOGAMEVERSION 0x21E +#define DEMOGAMEVERSION 0x21F // Minimum demo version we can play. // Bump it whenever you change or remove existing DEM_ commands. diff --git a/src/win32/i_input.cpp b/src/win32/i_input.cpp index 3e265d762..8e1f62306 100644 --- a/src/win32/i_input.cpp +++ b/src/win32/i_input.cpp @@ -101,6 +101,7 @@ #include "d_event.h" #include "v_text.h" #include "version.h" +#include "events.h" // Prototypes and declarations. #include "rawinput.h" @@ -187,6 +188,10 @@ static void I_CheckGUICapture () wantCapt = (menuactive == MENU_On || menuactive == MENU_OnNoPause); } + // [ZZ] check active event handlers that want the UI processing + if (!wantCapt && E_CheckUiProcessors()) + wantCapt = true; + if (wantCapt != GUICapture) { GUICapture = wantCapt; diff --git a/src/win32/i_mouse.cpp b/src/win32/i_mouse.cpp index b4f82fdb3..d13ef2445 100644 --- a/src/win32/i_mouse.cpp +++ b/src/win32/i_mouse.cpp @@ -18,6 +18,7 @@ #include "win32iface.h" #include "rawinput.h" #include "menu/menu.h" +#include "events.h" // MACROS ------------------------------------------------------------------ @@ -282,6 +283,9 @@ void I_CheckNativeMouse(bool preferNative) } } + if (!want_native && E_CheckRequireMouse()) + want_native = true; + //Printf ("%d %d %d\n", wantNative, preferNative, NativeMouse); if (want_native != NativeMouse) diff --git a/wadsrc/static/zscript.txt b/wadsrc/static/zscript.txt index b423abae6..d755c85b9 100644 --- a/wadsrc/static/zscript.txt +++ b/wadsrc/static/zscript.txt @@ -5,6 +5,7 @@ #include "zscript/constants.txt" #include "zscript/actor.txt" #include "zscript/actor_checks.txt" +#include "zscript/events.txt" #include "zscript/menu/menuitembase.txt" #include "zscript/menu/menu.txt" diff --git a/wadsrc/static/zscript/events.txt b/wadsrc/static/zscript/events.txt new file mode 100755 index 000000000..24707055f --- /dev/null +++ b/wadsrc/static/zscript/events.txt @@ -0,0 +1,345 @@ +class BaseEvent native { } // just a base class. it doesn't inherit from Object on the scripting side so you can't call Destroy() on it and break everything. + +class RenderEvent : BaseEvent native +{ + native readonly Vector3 ViewPos; + native readonly double ViewAngle; + native readonly double ViewPitch; + native readonly double ViewRoll; + native readonly double FracTic; + native readonly Actor Camera; +} + +class WorldEvent : BaseEvent native +{ + // for loaded/unloaded + native readonly bool IsSaveGame; + // this will be true if we are re-entering the hub level. + native readonly bool IsReopen; + // for thingspawned/thingdied/thingdestroyed + native readonly Actor Thing; + // for thingdied. can be null + native readonly Actor Inflictor; + // for thingdamaged. + native readonly int Damage; + native readonly Actor DamageSource; + native readonly Name DamageType; + native readonly EDmgFlags DamageFlags; + native readonly double DamageAngle; +} + +class PlayerEvent : BaseEvent native +{ + // this is the player number that caused the event. + // note: you can get player struct from this by using players[e.PlayerNumber] + native readonly int PlayerNumber; + // this will be true if we are re-entering the hub level. + native readonly bool IsReturn; +} + +class UiEvent : BaseEvent native +{ + // d_gui.h + enum EGUIEvent + { + Type_None, + Type_KeyDown, + Type_KeyRepeat, + Type_KeyUp, + Type_Char, + Type_FirstMouseEvent, // ? + Type_MouseMove, + Type_LButtonDown, + Type_LButtonUp, + Type_LButtonClick, + Type_MButtonDown, + Type_MButtonUp, + Type_MButtonClick, + Type_RButtonDown, + Type_RButtonUp, + Type_RButtonClick, + Type_WheelUp, + Type_WheelDown, + Type_WheelRight, // ??? + Type_WheelLeft, // ??? + Type_BackButtonDown, // ??? + Type_BackButtonUp, // ??? + Type_FwdButtonDown, // ??? + Type_FwdButtonUp, // ??? + Type_LastMouseEvent + } + + // for KeyDown, KeyRepeat, KeyUp + enum ESpecialGUIKeys + { + Key_PgDn = 1, + Key_PgUp = 2, + Key_Home = 3, + Key_End = 4, + Key_Left = 5, + Key_Right = 6, + Key_Alert = 7, // ASCII bell + Key_Backspace = 8, // ASCII + Key_Tab = 9, // ASCII + Key_LineFeed = 10, // ASCII + Key_Down = 10, + Key_VTab = 11, // ASCII + Key_Up = 11, + Key_FormFeed = 12, // ASCII + Key_Return = 13, // ASCII + Key_F1 = 14, + Key_F2 = 15, + Key_F3 = 16, + Key_F4 = 17, + Key_F5 = 18, + Key_F6 = 19, + Key_F7 = 20, + Key_F8 = 21, + Key_F9 = 22, + Key_F10 = 23, + Key_F11 = 24, + Key_F12 = 25, + Key_Del = 26, + Key_Escape = 27, // ASCII + Key_Free1 = 28, + Key_Free2 = 29, + Key_Back = 30, // browser back key + Key_CEscape = 31 // color escape + } + + // + native readonly EGUIEvent Type; + // + native readonly String KeyString; + native readonly int KeyChar; + // + native readonly int MouseX; + native readonly int MouseY; + // + native readonly bool IsShift; + native readonly bool IsCtrl; + native readonly bool IsAlt; +} + +class InputEvent : BaseEvent native +{ + enum EGenericEvent + { + Type_None, + Type_KeyDown, + Type_KeyUp, + Type_Mouse, + Type_GUI, // unused, kept for completeness + Type_DeviceChange + } + + // ew. + enum EDoomInputKeys + { + Key_Pause = 0xc5, // DIK_PAUSE + Key_RightArrow = 0xcd, // DIK_RIGHT + Key_LeftArrow = 0xcb, // DIK_LEFT + Key_UpArrow = 0xc8, // DIK_UP + Key_DownArrow = 0xd0, // DIK_DOWN + Key_Escape = 0x01, // DIK_ESCAPE + Key_Enter = 0x1c, // DIK_RETURN + Key_Space = 0x39, // DIK_SPACE + Key_Tab = 0x0f, // DIK_TAB + Key_F1 = 0x3b, // DIK_F1 + Key_F2 = 0x3c, // DIK_F2 + Key_F3 = 0x3d, // DIK_F3 + Key_F4 = 0x3e, // DIK_F4 + Key_F5 = 0x3f, // DIK_F5 + Key_F6 = 0x40, // DIK_F6 + Key_F7 = 0x41, // DIK_F7 + Key_F8 = 0x42, // DIK_F8 + Key_F9 = 0x43, // DIK_F9 + Key_F10 = 0x44, // DIK_F10 + Key_F11 = 0x57, // DIK_F11 + Key_F12 = 0x58, // DIK_F12 + Key_Grave = 0x29, // DIK_GRAVE + + Key_Backspace = 0x0e, // DIK_BACK + + Key_Equals = 0x0d, // DIK_EQUALS + Key_Minus = 0x0c, // DIK_MINUS + + Key_LShift = 0x2A, // DIK_LSHIFT + Key_LCtrl = 0x1d, // DIK_LCONTROL + Key_LAlt = 0x38, // DIK_LMENU + + Key_RShift = Key_LSHIFT, + Key_RCtrl = Key_LCTRL, + Key_RAlt = Key_LALT, + + Key_Ins = 0xd2, // DIK_INSERT + Key_Del = 0xd3, // DIK_DELETE + Key_End = 0xcf, // DIK_END + Key_Home = 0xc7, // DIK_HOME + Key_PgUp = 0xc9, // DIK_PRIOR + Key_PgDn = 0xd1, // DIK_NEXT + + Key_Mouse1 = 0x100, + Key_Mouse2 = 0x101, + Key_Mouse3 = 0x102, + Key_Mouse4 = 0x103, + Key_Mouse5 = 0x104, + Key_Mouse6 = 0x105, + Key_Mouse7 = 0x106, + Key_Mouse8 = 0x107, + + Key_FirstJoyButton = 0x108, + Key_Joy1 = (Key_FirstJoyButton+0), + Key_Joy2 = (Key_FirstJoyButton+1), + Key_Joy3 = (Key_FirstJoyButton+2), + Key_Joy4 = (Key_FirstJoyButton+3), + Key_Joy5 = (Key_FirstJoyButton+4), + Key_Joy6 = (Key_FirstJoyButton+5), + Key_Joy7 = (Key_FirstJoyButton+6), + Key_Joy8 = (Key_FirstJoyButton+7), + Key_LastJoyButton = 0x187, + Key_JoyPOV1_Up = 0x188, + Key_JoyPOV1_Right = 0x189, + Key_JoyPOV1_Down = 0x18a, + Key_JoyPOV1_Left = 0x18b, + Key_JoyPOV2_Up = 0x18c, + Key_JoyPOV3_Up = 0x190, + Key_JoyPOV4_Up = 0x194, + + Key_MWheelUp = 0x198, + Key_MWheelDown = 0x199, + Key_MWheelRight = 0x19A, + Key_MWheelLeft = 0x19B, + + Key_JoyAxis1Plus = 0x19C, + Key_JoyAxis1Minus = 0x19D, + Key_JoyAxis2Plus = 0x19E, + Key_JoyAxis2Minus = 0x19F, + Key_JoyAxis3Plus = 0x1A0, + Key_JoyAxis3Minus = 0x1A1, + Key_JoyAxis4Plus = 0x1A2, + Key_JoyAxis4Minus = 0x1A3, + Key_JoyAxis5Plus = 0x1A4, + Key_JoyAxis5Minus = 0x1A5, + Key_JoyAxis6Plus = 0x1A6, + Key_JoyAxis6Minus = 0x1A7, + Key_JoyAxis7Plus = 0x1A8, + Key_JoyAxis7Minus = 0x1A9, + Key_JoyAxis8Plus = 0x1AA, + Key_JoyAxis8Minus = 0x1AB, + Num_JoyAxisButtons = 8, + + Key_Pad_LThumb_Right = 0x1AC, + Key_Pad_LThumb_Left = 0x1AD, + Key_Pad_LThumb_Down = 0x1AE, + Key_Pad_LThumb_Up = 0x1AF, + + Key_Pad_RThumb_Right = 0x1B0, + Key_Pad_RThumb_Left = 0x1B1, + Key_Pad_RThumb_Down = 0x1B2, + Key_Pad_RThumb_Up = 0x1B3, + + Key_Pad_DPad_Up = 0x1B4, + Key_Pad_DPad_Down = 0x1B5, + Key_Pad_DPad_Left = 0x1B6, + Key_Pad_DPad_Right = 0x1B7, + Key_Pad_Start = 0x1B8, + Key_Pad_Back = 0x1B9, + Key_Pad_LThumb = 0x1BA, + Key_Pad_RThumb = 0x1BB, + Key_Pad_LShoulder = 0x1BC, + Key_Pad_RShoulder = 0x1BD, + Key_Pad_LTrigger = 0x1BE, + Key_Pad_RTrigger = 0x1BF, + Key_Pad_A = 0x1C0, + Key_Pad_B = 0x1C1, + Key_Pad_X = 0x1C2, + Key_Pad_Y = 0x1C3, + + Num_Keys = 0x1C4 + } + + // + native readonly EGenericEvent Type; + // + native readonly int KeyScan; // as in EDoomInputKeys enum + native readonly String KeyString; + native readonly int KeyChar; // ASCII char (if any) + // + native readonly int MouseX; + native readonly int MouseY; +} + +class ConsoleEvent : BaseEvent native +{ + // for net events, this will be the activator. + // for UI events, this is always -1, and you need to check if level is loaded and use players[consoleplayer]. + native readonly int Player; + native readonly String Name; + native readonly int Args[3]; +} + +class StaticEventHandler : Object native +{ + // static event handlers CAN register other static event handlers. + // unlike EventHandler.Create that will not create them. + protected static native StaticEventHandler Create(class type); + protected static native StaticEventHandler CreateOnce(class type); + protected static native StaticEventHandler Find(Class type); // just for convenience. who knows. + + protected static native bool Register(StaticEventHandler handler); + protected static native bool Unregister(StaticEventHandler handler); + + // these are called when the handler gets registered or unregistered + // you can set Order/IsUiProcessor here. + virtual native void OnRegister(); + virtual native void OnUnregister(); + + // actual handlers are here + virtual native void WorldLoaded(WorldEvent e); + virtual native void WorldUnloaded(WorldEvent e); + virtual native void WorldThingSpawned(WorldEvent e); + virtual native void WorldThingDied(WorldEvent e); + virtual native void WorldThingRevived(WorldEvent e); + virtual native void WorldThingDamaged(WorldEvent e); + virtual native void WorldThingDestroyed(WorldEvent e); + virtual native void WorldLightning(WorldEvent e); // for the sake of completeness. + virtual native void WorldTick(WorldEvent e); + + // + virtual native void RenderFrame(RenderEvent e); + virtual native void RenderOverlay(RenderEvent e); + + // + virtual native void PlayerEntered(PlayerEvent e); + virtual native void PlayerRespawned(PlayerEvent e); + virtual native void PlayerDied(PlayerEvent e); + virtual native void PlayerDisconnected(PlayerEvent e); + + // + virtual native bool UiProcess(UiEvent e); + virtual native bool InputProcess(InputEvent e); + + // + virtual native void ConsoleProcess(ConsoleEvent e); + + // this value will be queried on Register() to decide the relative order of this handler to every other. + // this is most useful in UI systems. + // default is 0. + native readonly int Order; + native void SetOrder(int order); + // this value will be queried on user input to decide whether to send UiProcess to this handler. + native bool IsUiProcessor; + // this value determines whether mouse input is required. + native bool RequireMouse; +} + +class EventHandler : StaticEventHandler native +{ + static native StaticEventHandler Create(class type); + static native StaticEventHandler CreateOnce(class type); + static native StaticEventHandler Find(class type); + + static native bool Register(StaticEventHandler handler); + static native bool Unregister(StaticEventHandler handler); +}